Use pre-fetching for ANALYZE
authorStephen Frost <sfrost@snowman.net>
Tue, 16 Mar 2021 18:46:48 +0000 (14:46 -0400)
committerStephen Frost <sfrost@snowman.net>
Tue, 16 Mar 2021 18:46:48 +0000 (14:46 -0400)
When we have posix_fadvise() available, we can improve the performance
of an ANALYZE by quite a bit by using it to inform the kernel of the
blocks that we're going to be asking for.  Similar to bitmap index
scans, the number of buffers pre-fetched is based off of the
maintenance_io_concurrency setting (for the particular tablespace or,
if not set, globally, via get_tablespace_maintenance_io_concurrency()).

Reviewed-By: Heikki Linnakangas, Tomas Vondra
Discussion: https://www.postgresql.org/message-id/VI1PR0701MB69603A433348EDCF783C6ECBF6EF0%40VI1PR0701MB6960.eurprd07.prod.outlook.com

src/backend/commands/analyze.c

index 596bab2f104431c65a8575ac10f04e7c4b354583..f84616d3d2c6070335a8549da99d1281271f6a2f 100644 (file)
@@ -63,6 +63,7 @@
 #include "utils/pg_rusage.h"
 #include "utils/sampling.h"
 #include "utils/sortsupport.h"
+#include "utils/spccache.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
@@ -1127,6 +1128,7 @@ acquire_sample_rows(Relation onerel, int elevel,
        double          liverows = 0;   /* # live rows seen */
        double          deadrows = 0;   /* # dead rows seen */
        double          rowstoskip = -1;        /* -1 means not set yet */
+       long            randseed;               /* Seed for block sampler(s) */
        BlockNumber totalblocks;
        TransactionId OldestXmin;
        BlockSamplerData bs;
@@ -1135,6 +1137,10 @@ 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
 
        Assert(targrows > 0);
 
@@ -1144,7 +1150,15 @@ acquire_sample_rows(Relation onerel, int elevel,
        OldestXmin = GetOldestNonRemovableTransactionId(onerel);
 
        /* Prepare for sampling block numbers */
-       nblocks = BlockSampler_Init(&bs, totalblocks, targrows, random());
+       randseed = random();
+       nblocks = BlockSampler_Init(&bs, totalblocks, targrows, randseed);
+
+#ifdef USE_PREFETCH
+       prefetch_maximum = get_tablespace_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,
@@ -1156,14 +1170,69 @@ acquire_sample_rows(Relation onerel, int elevel,
        scan = table_beginscan_analyze(onerel);
        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
+
        /* 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;
+
+               /*
+                * 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();
 
-               if (!table_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
+
+               /*
+                * 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))