On 2019-Feb-13, Amit Langote wrote: > Doesn't the name amphasename sound a bit too generic, given that it can > only describe the phases of ambuild? Maybe ambuildphase?
Hmm, yeah, maybe it does. I renamed it "ambuildphasename", since it's not about reporting the phase itself -- it's about translating the phase number to the string that's reported to the user. The attached patch does it that way. Also, when an index build uses an AM that doesn't support progress reporting, it no longer reports a NULL phase name while building. I also changed it to report the progress of phase 7 (heap scan validation) using block numbers rather than tuple counts. I also tweaked the strings reported in the view. They're clearer now IMO. One slight annoyance is that when parallel workers are used, the last block number reported in phase 3/subphase 2 (IndexBuildHeapScan stuff) is not necessarily accurate, since the tail of the table could well be scanned by a worker that's not the leader, and we only report in the leader when it gets a new block. When the AM does not support progress reporting, everything stays as zeros during the index build phase. It's easy to notice how slow hash indexes are to build compared to btrees this way! Maybe it'd be better fallback on reporting block numbers in IndexBuildHeapScan when this happens. Thoughts? I added docs to the monitoring section -- that's the bulkiest part of the patch. -- Álvaro Herrera https://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>From e8eae6d3d5af45ecb45171745a4af374013baffe Mon Sep 17 00:00:00 2001 From: Alvaro Herrera <alvhe...@alvh.no-ip.org> Date: Wed, 2 Jan 2019 16:14:39 -0300 Subject: [PATCH] Report progress of CREATE INDEX operations --- contrib/amcheck/verify_nbtree.c | 2 +- contrib/bloom/blinsert.c | 2 +- contrib/bloom/blutils.c | 1 + doc/src/sgml/indexam.sgml | 13 ++ doc/src/sgml/monitoring.sgml | 227 +++++++++++++++++++++++++- src/backend/access/brin/brin.c | 5 +- src/backend/access/gin/gininsert.c | 2 +- src/backend/access/gin/ginutil.c | 1 + src/backend/access/gist/gist.c | 1 + src/backend/access/gist/gistbuild.c | 2 +- src/backend/access/hash/hash.c | 3 +- src/backend/access/nbtree/nbtree.c | 9 + src/backend/access/nbtree/nbtsort.c | 43 ++++- src/backend/access/nbtree/nbtutils.c | 24 +++ src/backend/access/spgist/spginsert.c | 2 +- src/backend/access/spgist/spgutils.c | 1 + src/backend/catalog/index.c | 134 ++++++++++++++- src/backend/catalog/system_views.sql | 27 +++ src/backend/commands/indexcmds.c | 52 +++++- src/backend/storage/ipc/standby.c | 2 +- src/backend/storage/lmgr/lmgr.c | 46 +++++- src/backend/storage/lmgr/lock.c | 7 +- src/backend/utils/adt/amutils.c | 23 +++ src/backend/utils/adt/pgstatfuncs.c | 2 + src/include/access/amapi.h | 4 + src/include/access/genam.h | 1 + src/include/access/nbtree.h | 11 ++ src/include/catalog/index.h | 2 + src/include/catalog/pg_proc.dat | 10 +- src/include/commands/progress.h | 36 ++++ src/include/pgstat.h | 5 +- src/include/storage/lmgr.h | 4 +- src/include/storage/lock.h | 2 +- 33 files changed, 668 insertions(+), 38 deletions(-) diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c index 964200a7678..99f6ed6bc44 100644 --- a/contrib/amcheck/verify_nbtree.c +++ b/contrib/amcheck/verify_nbtree.c @@ -534,7 +534,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool readonly, RelationGetRelationName(state->rel), RelationGetRelationName(state->heaprel)); - IndexBuildHeapScan(state->heaprel, state->rel, indexinfo, true, + IndexBuildHeapScan(state->heaprel, state->rel, indexinfo, true, false, bt_tuple_present_callback, (void *) state, scan); ereport(DEBUG1, diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c index e43fbe0005f..947ee74881f 100644 --- a/contrib/bloom/blinsert.c +++ b/contrib/bloom/blinsert.c @@ -141,7 +141,7 @@ blbuild(Relation heap, Relation index, IndexInfo *indexInfo) initCachedPage(&buildstate); /* Do the heap scan */ - reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, + reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, false, bloomBuildCallback, (void *) &buildstate, NULL); diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c index 64583765787..697a37a384b 100644 --- a/contrib/bloom/blutils.c +++ b/contrib/bloom/blutils.c @@ -132,6 +132,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amcostestimate = blcostestimate; amroutine->amoptions = bloptions; amroutine->amproperty = NULL; + amroutine->ambuildphasename = NULL; amroutine->amvalidate = blvalidate; amroutine->ambeginscan = blbeginscan; amroutine->amrescan = blrescan; diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml index 05102724ead..fa4a3d0d131 100644 --- a/doc/src/sgml/indexam.sgml +++ b/doc/src/sgml/indexam.sgml @@ -127,6 +127,7 @@ typedef struct IndexAmRoutine amcostestimate_function amcostestimate; amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ + ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; ambeginscan_function ambeginscan; amrescan_function amrescan; @@ -468,6 +469,18 @@ amproperty (Oid index_oid, int attno, <para> <programlisting> +char * +ambuildphasename (int64 phasenum); +</programlisting> + Return the textual name of the given build phase number. + The phase numbers are those reported during an index build via the + <function>pgstat_progress_update_param</function> interface. + The phase names are then exposed in the + <structname>pg_stat_progress_create_index</structname> view. + </para> + + <para> +<programlisting> bool amvalidate (Oid opclassoid); </programlisting> diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 0e73cdcddab..47708570f84 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -336,6 +336,14 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry><structname>pg_stat_progress_create_index</structname><indexterm><primary>pg_stat_progress_create_index</primary></indexterm></entry> + <entry>One row for each backend running <command>CREATE INDEX</command>, showing + current progress. + See <xref linkend='create-index-progress-reporting'/>. + </entry> + </row> + <row> <entry><structname>pg_stat_progress_vacuum</structname><indexterm><primary>pg_stat_progress_vacuum</primary></indexterm></entry> <entry>One row for each backend (including autovacuum worker processes) running @@ -3376,11 +3384,224 @@ SELECT pg_stat_get_backend_pid(s.backendid) AS pid, <para> <productname>PostgreSQL</productname> has the ability to report the progress of - certain commands during command execution. Currently, the only command - which supports progress reporting is <command>VACUUM</command>. This may be - expanded in the future. + certain commands during command execution. Currently, the only commands + which support progress reporting are <command>CREATE INDEX</command> and + <command>VACUUM</command>. This may be expanded in the future. </para> + <sect2 id="create-index-progress-reporting"> + <title>CREATE INDEX Progress Reporting</title> + + <para> + Whenever <command>CREATE INDEX</command> is running, the + <structname>pg_stat_progress_create_index</structname> view will contain + one row for each backend that is currently creating indexes. The tables + below describe the information that will be reported and provide information + about how to interpret it. + </para> + + <table id="pg-stat-progress-create-index-view" xreflabel="pg_stat_progress_create_index"> + <title><structname>pg_stat_progress_create_index</structname> View</title> + <tgroup cols="3"> + <thead> + <row> + <entry>Column</entry> + <entry>Type</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + <row> + <entry><structfield>pid</structfield></entry> + <entry><type>integer</type></entry> + <entry>Process ID of backend.</entry> + </row> + <row> + <entry><structfield>datid</structfield></entry> + <entry><type>oid</type></entry> + <entry>OID of the database to which this backend is connected.</entry> + </row> + <row> + <entry><structfield>datname</structfield></entry> + <entry><type>name</type></entry> + <entry>Name of the database to which this backend is connected.</entry> + </row> + <row> + <entry><structfield>relid</structfield></entry> + <entry><type>oid</type></entry> + <entry>OID of the table on which the index is being created.</entry> + </row> + <row> + <entry><structfield>phase</structfield></entry> + <entry><type>text</type></entry> + <entry> + Current processing phase of index creation. See <xref linkend='create-index-phases'/>. + </entry> + </row> + <row> + <entry><structfield>lockers_total</structfield></entry> + <entry><type>bigint</type></entry> + <entry> + Total number of lockers to wait for, when applicable. + </entry> + </row> + <row> + <entry><structfield>lockers_done</structfield></entry> + <entry><type>bigint</type></entry> + <entry> + Number of lockers already waited for. + </entry> + </row> + <row> + <entry><structfield>current_locked_pid</structfield></entry> + <entry><type>bigint</type></entry> + <entry> + Process ID of the locker currently being waited for. + </entry> + </row> + <row> + <entry><structfield>blocks_total</structfield></entry> + <entry><type>bigint</type></entry> + <entry> + Total number of blocks to be processed in the current phase. + </entry> + </row> + <row> + <entry><structfield>blocks_done</structfield></entry> + <entry><type>bigint</type></entry> + <entry> + Number of blocks already processed in the current phase. + </entry> + </row> + <row> + <entry><structfield>tuples_total</structfield></entry> + <entry><type>bigint</type></entry> + <entry> + Total number of tuples to be processed in the current phase. + </entry> + </row> + <row> + <entry><structfield>tuples_done</structfield></entry> + <entry><type>bigint</type></entry> + <entry> + Number of tuples already processed in the current phase. + </entry> + </row> + <row> + <entry><structfield>partitions_total</structfield></entry> + <entry><type>bigint</type></entry> + <entry> + When creating an index on a partitioned, this column is set to the + total number of partitions on which the index is to be created. + </entry> + </row> + <row> + <entry><structfield>partitions_done</structfield></entry> + <entry><type>bigint</type></entry> + <entry> + When creating an index on a partitioned, this column is set to the + number of partitions on which the index has been completed. + </entry> + </row> + </tbody> + </tgroup> + </table> + + <table id="create-index-phases"> + <title>CREATE INDEX phases</title> + <tgroup cols="2"> + <thead> + <row> + <entry>Phase</entry> + <entry>Description</entry> + </row> + </thead> + <tbody> + <row> + <entry><literal>initializing</literal></entry> + <entry> + <command>CREATE INDEX</command> is preparing to create the index. This + phase is expected to be very brief. + </entry> + </row> + <row> + <entry><literal>waiting for old snapshots</literal></entry> + <entry> + <command>CREATE INDEX CONCURRENTLY</command> is waiting for transactions + that can potentially see the table to release their snapshots. + This phase is skipped when not in concurrent mode. + Columns <structname>lockers_total</structname>, <structname>lockers_done</structname> + and <structname>current_locker_pid</structname> contain the progress + information for this phase. + </entry> + </row> + <row> + <entry><literal>building index</literal></entry> + <entry> + The index is being built by the access method-specific code. In this phase, + access methods that support progress reporting fill in their own progress data, + and the subphase is indicated in this column. Typically, + <structname>blocks_total</structname> and <structname>blocks_done</structname> + will contain progress data, as well as potentially + <structname>tuples_total</structname> and <structname>tuples_done</structname>. + </entry> + </row> + <row> + <entry><literal>waiting for writer snapshots</literal></entry> + <entry> + <command>CREATE INDEX CONCURRENTLY</command> is waiting for transactions + that can potentially write into the table to release their snapshots. + This phase is skipped when not in concurrent mode. + Columns <structname>lockers_total</structname>, <structname>lockers_done</structname> + and <structname>current_locker_pid</structname> contain the progress + information for this phase. + </entry> + </row> + <row> + <entry><literal>validating index scan</literal></entry> + <entry> + <command>CREATE INDEX CONCURRENTLY</command> is scanning the index searching + for tuples that need to be validated. + This phase is skipped when not in concurrent mode. + Columns <structname>blocks_total</structname> (set to the total size of the index) + and <structname>blocks_done</structname> contain the progress information for this phase. + </entry> + </row> + <row> + <entry><literal>sorting index scan results</literal></entry> + <entry> + <command>CREATE INDEX CONCURRENTLY</command> is sorting the output of the + previous phase. + </entry> + </row> + <row> + <entry><literal>validating index heapscan</literal></entry> + <entry> + <command>CREATE INDEX CONCURRENTLY</command> is scanning the table + to validate the index tuples collected in the previous two phases. + This phase is skipped when not in concurrent mode. + Columns <structname>blocks_total</structname> (set to the total size of the table) + and <structname>blocks_done</structname> contain the progress information for this phase. + </entry> + </row> + <row> + <entry><literal>waiting for reader snapshots</literal></entry> + <entry> + <command>CREATE INDEX CONCURRENTLY</command> is waiting for transactions + that can potentially see the table to release their snapshots. This + phase is skipped when not in concurrent mode. + Columns <structname>lockers_total</structname>, <structname>lockers_done</structname> + and <structname>current_locker_pid</structname> contain the progress + information for this phase. + </entry> + </row> + </tbody> + </tgroup> + </table> + + </sect2> + <sect2 id="vacuum-progress-reporting"> <title>VACUUM Progress Reporting</title> diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 8f008dd0080..5d1aff34080 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -111,6 +111,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amcostestimate = brincostestimate; amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; + amroutine->ambuildphasename = NULL; amroutine->amvalidate = brinvalidate; amroutine->ambeginscan = brinbeginscan; amroutine->amrescan = brinrescan; @@ -718,7 +719,7 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo) * Now scan the relation. No syncscan allowed here because we want the * heap blocks in physical order. */ - reltuples = IndexBuildHeapScan(heap, index, indexInfo, false, + reltuples = IndexBuildHeapScan(heap, index, indexInfo, false, false, brinbuildCallback, (void *) state, NULL); /* process the final batch */ @@ -1234,7 +1235,7 @@ summarize_range(IndexInfo *indexInfo, BrinBuildState *state, Relation heapRel, * by transactions that are still in progress, among other corner cases. */ state->bs_currRangeStart = heapBlk; - IndexBuildHeapRangeScan(heapRel, state->bs_irel, indexInfo, false, true, + IndexBuildHeapRangeScan(heapRel, state->bs_irel, indexInfo, false, true, false, heapBlk, scanNumBlks, brinbuildCallback, (void *) state, NULL); diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c index 524ac5be8b5..838de4c1ec3 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -394,7 +394,7 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo) * Do the heap scan. We disallow sync scan here because dataPlaceToPage * prefers to receive tuples in TID order. */ - reltuples = IndexBuildHeapScan(heap, index, indexInfo, false, + reltuples = IndexBuildHeapScan(heap, index, indexInfo, false, false, ginBuildCallback, (void *) &buildstate, NULL); /* dump remaining entries to the index */ diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index afc20232ace..d2360eeafb0 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -64,6 +64,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amcostestimate = gincostestimate; amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; + amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; amroutine->ambeginscan = ginbeginscan; amroutine->amrescan = ginrescan; diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index b75b3a8dacd..0dc36af1e0c 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -87,6 +87,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amcostestimate = gistcostestimate; amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; + amroutine->ambuildphasename = NULL; amroutine->amvalidate = gistvalidate; amroutine->ambeginscan = gistbeginscan; amroutine->amrescan = gistrescan; diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c index bd142a3560d..015f874cc93 100644 --- a/src/backend/access/gist/gistbuild.c +++ b/src/backend/access/gist/gistbuild.c @@ -204,7 +204,7 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo) /* * Do the heap scan. */ - reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, + reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, false, gistBuildCallback, (void *) &buildstate, NULL); /* diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index f1f01a0956d..fc7db5d6a13 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -82,6 +82,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amcostestimate = hashcostestimate; amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; + amroutine->ambuildphasename = NULL; amroutine->amvalidate = hashvalidate; amroutine->ambeginscan = hashbeginscan; amroutine->amrescan = hashrescan; @@ -159,7 +160,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo) buildstate.heapRel = heap; /* do the heap scan */ - reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, + reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, false, hashbuildCallback, (void *) &buildstate, NULL); if (buildstate.spool) diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 98917de2efd..f9d32c958f6 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -22,6 +22,7 @@ #include "access/nbtxlog.h" #include "access/relscan.h" #include "access/xlog.h" +#include "commands/progress.h" #include "commands/vacuum.h" #include "miscadmin.h" #include "nodes/execnodes.h" @@ -133,6 +134,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amcostestimate = btcostestimate; amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; + amroutine->ambuildphasename = btbuildphasename; amroutine->amvalidate = btvalidate; amroutine->ambeginscan = btbeginscan; amroutine->amrescan = btrescan; @@ -1021,6 +1023,10 @@ btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, if (needLock) UnlockRelationForExtension(rel, ExclusiveLock); + if (info->report_progress) + pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL, + num_pages); + /* Quit if we've scanned the whole relation */ if (blkno >= num_pages) break; @@ -1028,6 +1034,9 @@ btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, for (; blkno < num_pages; blkno++) { btvacuumpage(&vstate, blkno, blkno); + if (info->report_progress) + pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE, + blkno); } } diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c index dc398e11867..804280385aa 100644 --- a/src/backend/access/nbtree/nbtsort.c +++ b/src/backend/access/nbtree/nbtsort.c @@ -65,6 +65,7 @@ #include "access/xlog.h" #include "access/xloginsert.h" #include "catalog/index.h" +#include "commands/progress.h" #include "miscadmin.h" #include "pgstat.h" #include "storage/smgr.h" @@ -288,7 +289,8 @@ static double _bt_parallel_heapscan(BTBuildState *buildstate, static void _bt_leader_participate_as_worker(BTBuildState *buildstate); static void _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2, BTShared *btshared, Sharedsort *sharedsort, - Sharedsort *sharedsort2, int sortmem); + Sharedsort *sharedsort2, int sortmem, + bool progress); /* @@ -384,6 +386,10 @@ _bt_spools_heapscan(Relation heap, Relation index, BTBuildState *buildstate, /* Save as primary spool */ buildstate->spool = btspool; + /* Report heap scan phase started */ + pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, + PROGRESS_BTREE_PHASE_INDEXBUILD_HEAPSCAN); + /* Attempt to launch parallel worker scan when required */ if (indexInfo->ii_ParallelWorkers > 0) _bt_begin_parallel(buildstate, indexInfo->ii_Concurrent, @@ -470,13 +476,17 @@ _bt_spools_heapscan(Relation heap, Relation index, BTBuildState *buildstate, /* Fill spool using either serial or parallel heap scan */ if (!buildstate->btleader) - reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, + reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, true, _bt_build_callback, (void *) buildstate, NULL); else reltuples = _bt_parallel_heapscan(buildstate, &indexInfo->ii_BrokenHotChain); + /* Set the progress target for the next phase */ + pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_TOTAL, + buildstate->indtuples); + /* okay, all heap tuples are spooled */ if (buildstate->spool2 && !buildstate->havedead) { @@ -525,9 +535,15 @@ _bt_leafbuild(BTSpool *btspool, BTSpool *btspool2) } #endif /* BTREE_BUILD_STATS */ + pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, + PROGRESS_BTREE_PHASE_PERFORMSORT_1); tuplesort_performsort(btspool->sortstate); if (btspool2) + { + pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, + PROGRESS_BTREE_PHASE_PERFORMSORT_2); tuplesort_performsort(btspool2->sortstate); + } wstate.heap = btspool->heap; wstate.index = btspool->index; @@ -543,6 +559,8 @@ _bt_leafbuild(BTSpool *btspool, BTSpool *btspool2) wstate.btws_pages_written = 0; wstate.btws_zeropage = NULL; /* until needed */ + pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE, + PROGRESS_BTREE_PHASE_LEAF_LOAD); _bt_load(&wstate, btspool, btspool2); } @@ -1078,6 +1096,7 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2) keysz = IndexRelationGetNumberOfKeyAttributes(wstate->index); ScanKey indexScanKey = NULL; SortSupport sortKeys; + long tuples_done = 0L; if (merge) { @@ -1170,6 +1189,10 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2) _bt_buildadd(wstate, state, itup2); itup2 = tuplesort_getindextuple(btspool2->sortstate, true); } + + /* Report progress */ + pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE, + ++tuples_done); } pfree(sortKeys); } @@ -1184,6 +1207,10 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2) state = _bt_pagestate(wstate, 0); _bt_buildadd(wstate, state, itup); + + /* Report progress */ + pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE, + ++tuples_done); } } @@ -1318,6 +1345,10 @@ _bt_begin_parallel(BTBuildState *buildstate, bool isconcurrent, int request) btshared->brokenhotchain = false; heap_parallelscan_initialize(&btshared->heapdesc, btspool->heap, snapshot); + /* Report total number of blocks to scan */ + pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL, + btshared->heapdesc.phs_nblocks); + /* * Store shared tuplesort-private state, for which we reserved space. * Then, initialize opaque state using tuplesort routine. @@ -1493,7 +1524,7 @@ _bt_leader_participate_as_worker(BTBuildState *buildstate) /* Perform work common to all participants */ _bt_parallel_scan_and_sort(leaderworker, leaderworker2, btleader->btshared, btleader->sharedsort, btleader->sharedsort2, - sortmem); + sortmem, true); #ifdef BTREE_BUILD_STATS if (log_btree_build_stats) @@ -1584,7 +1615,7 @@ _bt_parallel_build_main(dsm_segment *seg, shm_toc *toc) /* Perform sorting of spool, and possibly a spool2 */ sortmem = maintenance_work_mem / btshared->scantuplesortstates; _bt_parallel_scan_and_sort(btspool, btspool2, btshared, sharedsort, - sharedsort2, sortmem); + sharedsort2, sortmem, false); #ifdef BTREE_BUILD_STATS if (log_btree_build_stats) @@ -1613,7 +1644,7 @@ _bt_parallel_build_main(dsm_segment *seg, shm_toc *toc) static void _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2, BTShared *btshared, Sharedsort *sharedsort, - Sharedsort *sharedsort2, int sortmem) + Sharedsort *sharedsort2, int sortmem, bool progress) { SortCoordinate coordinate; BTBuildState buildstate; @@ -1672,7 +1703,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2, indexInfo->ii_Concurrent = btshared->isconcurrent; scan = heap_beginscan_parallel(btspool->heap, &btshared->heapdesc); reltuples = IndexBuildHeapScan(btspool->heap, btspool->index, indexInfo, - true, _bt_build_callback, + true, progress, _bt_build_callback, (void *) &buildstate, scan); /* diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index 2c05fb5e451..295c22b2e5b 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -20,6 +20,7 @@ #include "access/nbtree.h" #include "access/reloptions.h" #include "access/relscan.h" +#include "commands/progress.h" #include "miscadmin.h" #include "utils/array.h" #include "utils/lsyscache.h" @@ -2082,6 +2083,29 @@ btproperty(Oid index_oid, int attno, } } +/* + * btbuildphasename() -- Return name of index build phase. + */ +char * +btbuildphasename(int64 phasenum) +{ + switch (phasenum) + { + case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE: + return "initializing (1 of 5)"; + case PROGRESS_BTREE_PHASE_INDEXBUILD_HEAPSCAN: + return "table scan (2 of 5)"; + case PROGRESS_BTREE_PHASE_PERFORMSORT_1: + return "sorting tuples, spool 1 (3 of 5)"; + case PROGRESS_BTREE_PHASE_PERFORMSORT_2: + return "sorting tuples, spool 2 (4 of 5)"; + case PROGRESS_BTREE_PHASE_LEAF_LOAD: + return "btree tuple loading (5 of 5)"; + default: + return NULL; + } +} + /* * _bt_nonkey_truncate() -- create tuple without non-key suffix attributes. * diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c index f428a151385..1bc671c7238 100644 --- a/src/backend/access/spgist/spginsert.c +++ b/src/backend/access/spgist/spginsert.c @@ -142,7 +142,7 @@ spgbuild(Relation heap, Relation index, IndexInfo *indexInfo) "SP-GiST build temporary context", ALLOCSET_DEFAULT_SIZES); - reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, + reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, false, spgistBuildCallback, (void *) &buildstate, NULL); diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index 8e63c1fad25..45472db147b 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -67,6 +67,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amcostestimate = spgcostestimate; amroutine->amoptions = spgoptions; amroutine->amproperty = spgproperty; + amroutine->ambuildphasename = NULL; amroutine->amvalidate = spgvalidate; amroutine->ambeginscan = spgbeginscan; amroutine->amrescan = spgrescan; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index d16c3d0ea50..4205491ec1f 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -49,8 +49,9 @@ #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/storage.h" -#include "commands/tablecmds.h" #include "commands/event_trigger.h" +#include "commands/progress.h" +#include "commands/tablecmds.h" #include "commands/trigger.h" #include "executor/executor.h" #include "miscadmin.h" @@ -58,6 +59,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "parser/parser.h" +#include "pgstat.h" #include "rewrite/rewriteManip.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" @@ -1597,7 +1599,7 @@ index_drop(Oid indexId, bool concurrent) * to acquire an exclusive lock on our table. The lock code will * detect deadlock and error out properly. */ - WaitForLockers(heaplocktag, AccessExclusiveLock); + WaitForLockers(heaplocktag, AccessExclusiveLock, true); /* * No more predicate locks will be acquired on this index, and we're @@ -1641,7 +1643,7 @@ index_drop(Oid indexId, bool concurrent) * Wait till every transaction that saw the old index state has * finished. */ - WaitForLockers(heaplocktag, AccessExclusiveLock); + WaitForLockers(heaplocktag, AccessExclusiveLock, true); /* * Re-open relations to allow us to complete our actions. @@ -2291,6 +2293,25 @@ index_build(Relation heapRelation, save_sec_context | SECURITY_RESTRICTED_OPERATION); save_nestlevel = NewGUCNestLevel(); + /* Set up initial progress report status */ + { + const int index[] = { + PROGRESS_CREATEIDX_PHASE, + PROGRESS_CREATEIDX_SUBPHASE, + PROGRESS_CREATEIDX_TUPLES_DONE, + PROGRESS_CREATEIDX_TUPLES_TOTAL, + PROGRESS_SCAN_BLOCKS_DONE, + PROGRESS_SCAN_BLOCKS_TOTAL + }; + const int64 val[] = { + PROGRESS_CREATEIDX_PHASE_BUILD, + PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE, + 0, 0, 0, 0 + }; + + pgstat_progress_update_multi_param(6, index, val); + } + /* * Call the access method's build procedure */ @@ -2428,13 +2449,14 @@ IndexBuildHeapScan(Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo, bool allow_sync, + bool progress, IndexBuildCallback callback, void *callback_state, HeapScanDesc scan) { return IndexBuildHeapRangeScan(heapRelation, indexRelation, indexInfo, allow_sync, - false, + false, progress, 0, InvalidBlockNumber, callback, callback_state, scan); } @@ -2455,6 +2477,7 @@ IndexBuildHeapRangeScan(Relation heapRelation, IndexInfo *indexInfo, bool allow_sync, bool anyvisible, + bool progress, BlockNumber start_blockno, BlockNumber numblocks, IndexBuildCallback callback, @@ -2476,6 +2499,8 @@ IndexBuildHeapRangeScan(Relation heapRelation, TransactionId OldestXmin; BlockNumber root_blkno = InvalidBlockNumber; OffsetNumber root_offsets[MaxHeapTuplesPerPage]; + BlockNumber blocks_done = 0; + BlockNumber previous_blkno = InvalidBlockNumber; /* * sanity checks @@ -2545,6 +2570,13 @@ IndexBuildHeapRangeScan(Relation heapRelation, NULL, /* scan key */ true, /* buffer access strategy OK */ allow_sync); /* syncscan OK? */ + + if (progress) + { + Assert(scan->rs_numblocks == InvalidBlockNumber); + pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL, + scan->rs_nblocks); + } } else { @@ -2592,6 +2624,46 @@ IndexBuildHeapRangeScan(Relation heapRelation, CHECK_FOR_INTERRUPTS(); + /* Report scan progress, if asked to. */ + if (progress && + ((previous_blkno == InvalidBlockNumber) || + (scan->rs_cblock != previous_blkno))) + { + /* we only do progress for full table scans */ + Assert(numblocks == InvalidBlockNumber); + + /* + * Report the number of blocks we've moved forward. + * + * Parallel workers cause the leader process to skip some blocks, + * so we subtract the current block number to the previous one to + * determine how many have been read in total; but be careful when + * we wrap around the last block in the table. + */ + if (scan->rs_cblock > previous_blkno) + blocks_done += scan->rs_cblock - previous_blkno; + else if (previous_blkno == InvalidBlockNumber) + { + /* + * How many blocks have been read since the scan started. + * Should normally be zero. + */ + blocks_done += scan->rs_cblock - + (scan->rs_parallel ? scan->rs_parallel->phs_startblock : + scan->rs_startblock); + Assert(blocks_done == 0); + } + else + { + /* wrapped around */ + blocks_done += scan->rs_nblocks - previous_blkno + scan->rs_cblock; + } + + pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE, + blocks_done); + previous_blkno = scan->rs_cblock; + } + /* * When dealing with a HOT-chain of updated tuples, we want to index * the values of the live tuple (if any), but index it under the TID @@ -3133,6 +3205,21 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot) int save_sec_context; int save_nestlevel; + { + const int index[] = { + PROGRESS_CREATEIDX_PHASE, + PROGRESS_CREATEIDX_TUPLES_DONE, + PROGRESS_CREATEIDX_TUPLES_TOTAL, + PROGRESS_SCAN_BLOCKS_DONE, + PROGRESS_SCAN_BLOCKS_TOTAL + }; + const int64 val[] = { + PROGRESS_CREATEIDX_PHASE_VALIDATE_IDXSCAN, + 0, 0, 0, 0 + }; + pgstat_progress_update_multi_param(5, index, val); + } + /* Open and lock the parent heap relation */ heapRelation = table_open(heapId, ShareUpdateExclusiveLock); /* And the target index relation */ @@ -3163,6 +3250,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot) */ ivinfo.index = indexRelation; ivinfo.analyze_only = false; + ivinfo.report_progress = true; /* XXX only for btree? */ ivinfo.estimated_count = true; ivinfo.message_level = DEBUG2; ivinfo.num_heap_tuples = heapRelation->rd_rel->reltuples; @@ -3180,15 +3268,39 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot) NULL, false); state.htups = state.itups = state.tups_inserted = 0; + /* ambulkdelete updates progress metrics */ (void) index_bulk_delete(&ivinfo, NULL, validate_index_callback, (void *) &state); /* Execute the sort */ + { + const int index[] = { + PROGRESS_CREATEIDX_PHASE, + PROGRESS_SCAN_BLOCKS_DONE, + PROGRESS_SCAN_BLOCKS_TOTAL + }; + const int64 val[] = { + PROGRESS_CREATEIDX_PHASE_SORT_IDXSCAN, + 0, 0 + }; + + pgstat_progress_update_multi_param(3, index, val); + } tuplesort_performsort(state.tuplesort); /* - * Now scan the heap and "merge" it with the index + * Now scan the heap and "merge" it with the index. */ + { + const int index[] = { + PROGRESS_CREATEIDX_PHASE, + }; + const int64 val[] = { + PROGRESS_CREATEIDX_PHASE_HEAPSCAN_VALIDATE + }; + + pgstat_progress_update_multi_param(1, index, val); + } validate_index_heapscan(heapRelation, indexRelation, indexInfo, @@ -3291,6 +3403,7 @@ validate_index_heapscan(Relation heapRelation, BlockNumber root_blkno = InvalidBlockNumber; OffsetNumber root_offsets[MaxHeapTuplesPerPage]; bool in_index[MaxHeapTuplesPerPage]; + BlockNumber previous_blkno = InvalidBlockNumber; /* state variables for the merge */ ItemPointer indexcursor = NULL; @@ -3330,6 +3443,9 @@ validate_index_heapscan(Relation heapRelation, true, /* buffer access strategy OK */ false); /* syncscan not OK */ + pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL, + scan->rs_nblocks); + /* * Scan all tuples matching the snapshot. */ @@ -3343,6 +3459,14 @@ validate_index_heapscan(Relation heapRelation, state->htups += 1; + if ((previous_blkno == InvalidBlockNumber) || + (scan->rs_cblock != previous_blkno)) + { + pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE, + scan->rs_cblock); + previous_blkno = scan->rs_cblock; + } + /* * As commented in IndexBuildHeapScan, we should index heap-only * tuples under the TIDs of their root tuples; so when we advance onto diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 3e229c693c4..3cbe6f8af2a 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -906,6 +906,33 @@ CREATE VIEW pg_stat_progress_vacuum AS FROM pg_stat_get_progress_info('VACUUM') AS S LEFT JOIN pg_database D ON S.datid = D.oid; +CREATE VIEW pg_stat_progress_create_index AS + SELECT + S.pid AS pid, S.datid AS datid, D.datname AS datname, + S.relid AS relid, + CASE S.param2 WHEN 0 THEN 'initializing (phase 1 of 8)' + WHEN 1 THEN 'waiting for old snapshots (phase 2 of 8)' + WHEN 2 THEN 'building index (3 of 8)' || + COALESCE((': ' || pg_indexam_progress_phasename(S.param1::oid, S.param3)), + '') + WHEN 3 THEN 'waiting for writer snapshots (phase 4 of 8)' + WHEN 4 THEN 'index validation: scan index (phase 5 of 8)' + WHEN 5 THEN 'index validation: sort index scan results (phase 6 of 8)' + WHEN 6 THEN 'index validation: scan heap (phase 7 of 8)' + WHEN 7 THEN 'waiting for reader snapshots (phase 8 of 8)' + END as phase, + S.param4 AS lockers_total, + S.param5 AS lockers_done, + S.param6 AS current_locker_pid, + S.param7 AS blocks_total, + S.param8 AS blocks_done, + S.param9 AS tuples_total, + S.param10 AS tuples_done, + S.param11 AS partitions_total, + S.param12 AS partitions_done + FROM pg_stat_get_progress_info('CREATE INDEX') AS S + LEFT JOIN pg_database D ON S.datid = D.oid; + CREATE VIEW pg_user_mappings AS SELECT U.oid AS umid, diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 5dcedc337aa..8f8587ac1e3 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -35,6 +35,7 @@ #include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/event_trigger.h" +#include "commands/progress.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" #include "mb/pg_wchar.h" @@ -46,10 +47,12 @@ #include "parser/parse_func.h" #include "parser/parse_oper.h" #include "partitioning/partdesc.h" +#include "pgstat.h" #include "rewrite/rewriteManip.h" #include "storage/lmgr.h" #include "storage/proc.h" #include "storage/procarray.h" +#include "storage/sinvaladt.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -368,6 +371,15 @@ DefineIndex(Oid relationId, Snapshot snapshot; int i; + + /* + * Start progress report. If we're building a partition, this was already + * done. + */ + if (!OidIsValid(parentIndexId)) + pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, + relationId); + /* * count key attributes in index */ @@ -584,6 +596,9 @@ DefineIndex(Oid relationId, accessMethodId = accessMethodForm->oid; amRoutine = GetIndexAmRoutine(accessMethodForm->amhandler); + pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID, + accessMethodId); + if (stmt->unique && !amRoutine->amcanunique) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -864,6 +879,11 @@ DefineIndex(Oid relationId, if (!OidIsValid(indexRelationId)) { table_close(rel, NoLock); + + /* If this is the top-level index, we're done */ + if (!OidIsValid(parentIndexId)) + pgstat_progress_end_command(); + return address; } @@ -889,6 +909,9 @@ DefineIndex(Oid relationId, TupleDesc parentDesc; Oid *opfamOids; + pgstat_progress_update_param(PROGRESS_CREATEIDX_PARTITIONS_TOTAL, + nparts); + memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts); parentDesc = CreateTupleDescCopy(RelationGetDescr(rel)); @@ -1039,6 +1062,8 @@ DefineIndex(Oid relationId, skip_build, quiet); } + pgstat_progress_update_param(PROGRESS_CREATEIDX_PARTITIONS_DONE, + i + 1); pfree(attmap); } @@ -1073,6 +1098,8 @@ DefineIndex(Oid relationId, * Indexes on partitioned tables are not themselves built, so we're * done here. */ + if (!OidIsValid(parentIndexId)) + pgstat_progress_end_command(); return address; } @@ -1080,6 +1107,11 @@ DefineIndex(Oid relationId, { /* Close the heap and we're done, in the non-concurrent case */ table_close(rel, NoLock); + + /* If this is the top-level index, we're done. */ + if (!OidIsValid(parentIndexId)) + pgstat_progress_end_command(); + return address; } @@ -1131,7 +1163,9 @@ DefineIndex(Oid relationId, * exclusive lock on our table. The lock code will detect deadlock and * error out properly. */ - WaitForLockers(heaplocktag, ShareLock); + pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE, + PROGRESS_CREATEIDX_PHASE_WAIT_1); + WaitForLockers(heaplocktag, ShareLock, true); /* * At this moment we are sure that there are no transactions with the @@ -1195,7 +1229,9 @@ DefineIndex(Oid relationId, * We once again wait until no transaction can have the table open with * the index marked as read-only for updates. */ - WaitForLockers(heaplocktag, ShareLock); + pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE, + PROGRESS_CREATEIDX_PHASE_WAIT_2); + WaitForLockers(heaplocktag, ShareLock, true); /* * Now take the "reference snapshot" that will be used by validate_index() @@ -1281,6 +1317,9 @@ DefineIndex(Oid relationId, old_snapshots = GetCurrentVirtualXIDs(limitXmin, true, false, PROC_IS_AUTOVACUUM | PROC_IN_VACUUM, &n_old_snapshots); + pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE, + PROGRESS_CREATEIDX_PHASE_WAIT_3); + pgstat_progress_update_param(PROGRESS_WAITFOR_TOTAL, n_old_snapshots); for (i = 0; i < n_old_snapshots; i++) { @@ -1316,7 +1355,14 @@ DefineIndex(Oid relationId, } if (VirtualTransactionIdIsValid(old_snapshots[i])) + { + PGPROC *holder = BackendIdGetProc(old_snapshots[i].backendId); + pgstat_progress_update_param(PROGRESS_WAITFOR_CURRENT_PID, + holder->pid); VirtualXactLock(old_snapshots[i], true); + } + + pgstat_progress_update_param(PROGRESS_WAITFOR_DONE, i + 1); } /* @@ -1339,6 +1385,8 @@ DefineIndex(Oid relationId, */ UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock); + pgstat_progress_end_command(); + return address; } diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c index 4d10e57a803..a0a2b964703 100644 --- a/src/backend/storage/ipc/standby.c +++ b/src/backend/storage/ipc/standby.c @@ -401,7 +401,7 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag) */ VirtualTransactionId *backends; - backends = GetLockConflicts(&locktag, AccessExclusiveLock); + backends = GetLockConflicts(&locktag, AccessExclusiveLock, NULL); ResolveRecoveryConflictWithVirtualXIDs(backends, PROCSIG_RECOVERY_CONFLICT_LOCK); } diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c index e688ba81170..0b04b093782 100644 --- a/src/backend/storage/lmgr/lmgr.c +++ b/src/backend/storage/lmgr/lmgr.c @@ -19,9 +19,12 @@ #include "access/transam.h" #include "access/xact.h" #include "catalog/catalog.h" +#include "commands/progress.h" #include "miscadmin.h" +#include "pgstat.h" #include "storage/lmgr.h" #include "storage/procarray.h" +#include "storage/sinvaladt.h" #include "utils/inval.h" @@ -857,10 +860,12 @@ XactLockTableWaitErrorCb(void *arg) * after we obtained our initial list of lockers, we will not wait for them. */ void -WaitForLockersMultiple(List *locktags, LOCKMODE lockmode) +WaitForLockersMultiple(List *locktags, LOCKMODE lockmode, bool progress) { List *holders = NIL; ListCell *lc; + int total = 0; + int done = 0; /* Done if no locks to wait for */ if (list_length(locktags) == 0) @@ -870,10 +875,17 @@ WaitForLockersMultiple(List *locktags, LOCKMODE lockmode) foreach(lc, locktags) { LOCKTAG *locktag = lfirst(lc); + int count; - holders = lappend(holders, GetLockConflicts(locktag, lockmode)); + holders = lappend(holders, + GetLockConflicts(locktag, lockmode, + progress ? &count : NULL)); + total += count; } + if (progress) + pgstat_progress_update_param(PROGRESS_WAITFOR_TOTAL, total); + /* * Note: GetLockConflicts() never reports our own xid, hence we need not * check for that. Also, prepared xacts are not reported, which is fine @@ -887,10 +899,36 @@ WaitForLockersMultiple(List *locktags, LOCKMODE lockmode) while (VirtualTransactionIdIsValid(*lockholders)) { + /* + * If requested, publish who we're going to wait for. This is not + * 100% accurate if they're already gone, but we don't care. + */ + if (progress) + { + PGPROC *holder = BackendIdGetProc(lockholders->backendId); + + pgstat_progress_update_param(PROGRESS_WAITFOR_CURRENT_PID, + holder->pid); + } VirtualXactLock(*lockholders, true); lockholders++; + + if (progress) + pgstat_progress_update_param(PROGRESS_WAITFOR_DONE, ++done); } } + if (progress) + { + const int index[] = { + PROGRESS_WAITFOR_TOTAL, + PROGRESS_WAITFOR_DONE, + PROGRESS_WAITFOR_CURRENT_PID + }; + const int64 values[] = { + 0, 0, 0 + }; + pgstat_progress_update_multi_param(3, index, values); + } list_free_deep(holders); } @@ -901,12 +939,12 @@ WaitForLockersMultiple(List *locktags, LOCKMODE lockmode) * Same as WaitForLockersMultiple, for a single lock tag. */ void -WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode) +WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode, bool progress) { List *l; l = list_make1(&heaplocktag); - WaitForLockersMultiple(l, lockmode); + WaitForLockersMultiple(l, lockmode, progress); list_free(l); } diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 3bb5ce350aa..58ba90d0646 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -2807,6 +2807,7 @@ FastPathGetRelationLockEntry(LOCALLOCK *locallock) * xacts merely awaiting such a lock are NOT reported. * * The result array is palloc'd and is terminated with an invalid VXID. + * *countp, if not null, is updated to the number of items set. * * Of course, the result could be out of date by the time it's returned, * so use of this function has to be thought about carefully. @@ -2817,7 +2818,7 @@ FastPathGetRelationLockEntry(LOCALLOCK *locallock) * uses of the result. */ VirtualTransactionId * -GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode) +GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode, int *countp) { static VirtualTransactionId *vxids; LOCKMETHODID lockmethodid = locktag->locktag_lockmethodid; @@ -2964,6 +2965,8 @@ GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode) LWLockRelease(partitionLock); vxids[count].backendId = InvalidBackendId; vxids[count].localTransactionId = InvalidLocalTransactionId; + if (countp) + *countp = count; return vxids; } @@ -3019,6 +3022,8 @@ GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode) vxids[count].backendId = InvalidBackendId; vxids[count].localTransactionId = InvalidLocalTransactionId; + if (countp) + *countp = count; return vxids; } diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c index 060ffe501ec..e81d6cc0562 100644 --- a/src/backend/utils/adt/amutils.c +++ b/src/backend/utils/adt/amutils.c @@ -445,3 +445,26 @@ pg_index_column_has_property(PG_FUNCTION_ARGS) return indexam_property(fcinfo, propname, InvalidOid, relid, attno); } + +/* + * Return the name of the given phase, as used for progress reporting by the + * given AM. + */ +Datum +pg_indexam_progress_phasename(PG_FUNCTION_ARGS) +{ + Oid amoid = PG_GETARG_OID(0); + int32 phasenum = PG_GETARG_INT32(1); + IndexAmRoutine *routine; + char *name; + + routine = GetIndexAmRoutineByAmId(amoid, true); + if (routine == NULL || !routine->ambuildphasename) + PG_RETURN_NULL(); + + name = routine->ambuildphasename(phasenum); + if (!name) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(CStringGetTextDatum(name)); +} diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 69f72657792..05f9da6a4ad 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -468,6 +468,8 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS) /* Translate command name into command type code. */ if (pg_strcasecmp(cmd, "VACUUM") == 0) cmdtype = PROGRESS_COMMAND_VACUUM; + else if (pg_strcasecmp(cmd, "CREATE INDEX") == 0) + cmdtype = PROGRESS_COMMAND_CREATE_INDEX; else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 653ddc976ba..09a7404267c 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -108,6 +108,9 @@ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); +/* name of phase as used in progress reporting */ +typedef char *(*ambuildphasename_function) (int64 phasenum); + /* validate definition of an opclass for this AM */ typedef bool (*amvalidate_function) (Oid opclassoid); @@ -213,6 +216,7 @@ typedef struct IndexAmRoutine amcostestimate_function amcostestimate; amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ + ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; ambeginscan_function ambeginscan; amrescan_function amrescan; diff --git a/src/include/access/genam.h b/src/include/access/genam.h index c4aba39496f..f77d9eea8e8 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -45,6 +45,7 @@ typedef struct IndexVacuumInfo { Relation index; /* the index being vacuumed */ bool analyze_only; /* ANALYZE (without any actual vacuum) */ + bool report_progress; /* emit progress.h status reports */ bool estimated_count; /* num_heap_tuples is an estimate */ int message_level; /* ereport level for progress messages */ double num_heap_tuples; /* tuples remaining in heap */ diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 4fb92d60a12..18a6838ce56 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -488,6 +488,16 @@ typedef BTScanOpaqueData *BTScanOpaque; #define SK_BT_DESC (INDOPTION_DESC << SK_BT_INDOPTION_SHIFT) #define SK_BT_NULLS_FIRST (INDOPTION_NULLS_FIRST << SK_BT_INDOPTION_SHIFT) +/* + * Constant definition for progress reporting. Phase numbers must match + * btbuildphasename. + */ +/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 (see progress.h) */ +#define PROGRESS_BTREE_PHASE_INDEXBUILD_HEAPSCAN 2 +#define PROGRESS_BTREE_PHASE_PERFORMSORT_1 3 +#define PROGRESS_BTREE_PHASE_PERFORMSORT_2 4 +#define PROGRESS_BTREE_PHASE_LEAF_LOAD 5 + /* * external entry points for btree, in nbtree.c */ @@ -600,6 +610,7 @@ extern bytea *btoptions(Datum reloptions, bool validate); extern bool btproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); +extern char *btbuildphasename(int64 phasenum); extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup); extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum); diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 330c481a8b7..fb713d50f96 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -114,6 +114,7 @@ extern double IndexBuildHeapScan(Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo, bool allow_sync, + bool progress, IndexBuildCallback callback, void *callback_state, struct HeapScanDescData *scan); @@ -122,6 +123,7 @@ extern double IndexBuildHeapRangeScan(Relation heapRelation, IndexInfo *indexInfo, bool allow_sync, bool anyvisible, + bool progress, BlockNumber start_blockno, BlockNumber end_blockno, IndexBuildCallback callback, diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index a4e173b4846..32e31c2d3f1 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -917,6 +917,10 @@ proname => 'pg_index_column_has_property', provolatile => 's', prorettype => 'bool', proargtypes => 'regclass int4 text', prosrc => 'pg_index_column_has_property' }, +{ oid => '676', descr => 'return name of given index build phase', + proname => 'pg_indexam_progress_phasename', provolatile => 'i', + prorettype => 'text', proargtypes => 'oid int8', + prosrc => 'pg_indexam_progress_phasename' }, { oid => '339', proname => 'poly_same', prorettype => 'bool', @@ -5079,9 +5083,9 @@ proname => 'pg_stat_get_progress_info', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'record', proargtypes => 'text', - proallargtypes => '{text,int4,oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8}', - proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o}', - proargnames => '{cmdtype,pid,datid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10}', + proallargtypes => '{text,int4,oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8}', + proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{cmdtype,pid,datid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10,param11,param12}', prosrc => 'pg_stat_get_progress_info' }, { oid => '3099', descr => 'statistics: information about currently active replication', diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h index 9858b36a383..56bd6125594 100644 --- a/src/include/commands/progress.h +++ b/src/include/commands/progress.h @@ -34,4 +34,40 @@ #define PROGRESS_VACUUM_PHASE_TRUNCATE 5 #define PROGRESS_VACUUM_PHASE_FINAL_CLEANUP 6 + +/* Progress parameters for CREATE INDEX */ +#define PROGRESS_CREATEIDX_ACCESS_METHOD_OID 0 +#define PROGRESS_CREATEIDX_PHASE 1 /* AM-agnostic phase # */ +#define PROGRESS_CREATEIDX_SUBPHASE 2 /* phase # filled by AM */ +/* 3, 4 and 5 reserved for "waitfor" metrics */ +/* 6 and 7 reserved for "block number" metrics */ +#define PROGRESS_CREATEIDX_TUPLES_TOTAL 8 +#define PROGRESS_CREATEIDX_TUPLES_DONE 9 +#define PROGRESS_CREATEIDX_PARTITIONS_TOTAL 10 +#define PROGRESS_CREATEIDX_PARTITIONS_DONE 11 + +/* Phases of CREATE INDEX */ +#define PROGRESS_CREATEIDX_PHASE_WAIT_1 1 +#define PROGRESS_CREATEIDX_PHASE_BUILD 2 +#define PROGRESS_CREATEIDX_PHASE_WAIT_2 3 +#define PROGRESS_CREATEIDX_PHASE_VALIDATE_IDXSCAN 4 +#define PROGRESS_CREATEIDX_PHASE_SORT_IDXSCAN 5 +#define PROGRESS_CREATEIDX_PHASE_HEAPSCAN_VALIDATE 6 +#define PROGRESS_CREATEIDX_PHASE_WAIT_3 7 + +/* + * Subphases of CREATE INDEX, for index_build. + */ +#define PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE 1 +/* Additional phases are defined by each AM */ + +/* Lock holder wait counts */ +#define PROGRESS_WAITFOR_TOTAL 3 +#define PROGRESS_WAITFOR_DONE 4 +#define PROGRESS_WAITFOR_CURRENT_PID 5 + +/* Block numbers in a generic relation scan */ +#define PROGRESS_SCAN_BLOCKS_TOTAL 6 +#define PROGRESS_SCAN_BLOCKS_DONE 7 + #endif diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 88a75fb798e..f931ead1c27 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -934,10 +934,11 @@ typedef enum typedef enum ProgressCommandType { PROGRESS_COMMAND_INVALID, - PROGRESS_COMMAND_VACUUM + PROGRESS_COMMAND_VACUUM, + PROGRESS_COMMAND_CREATE_INDEX } ProgressCommandType; -#define PGSTAT_NUM_PROGRESS_PARAM 10 +#define PGSTAT_NUM_PROGRESS_PARAM 12 /* ---------- * Shared-memory data structures diff --git a/src/include/storage/lmgr.h b/src/include/storage/lmgr.h index 3d705faba5c..4f2872de35f 100644 --- a/src/include/storage/lmgr.h +++ b/src/include/storage/lmgr.h @@ -78,8 +78,8 @@ extern void XactLockTableWait(TransactionId xid, Relation rel, extern bool ConditionalXactLockTableWait(TransactionId xid); /* Lock VXIDs, specified by conflicting locktags */ -extern void WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode); -extern void WaitForLockersMultiple(List *locktags, LOCKMODE lockmode); +extern void WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode, bool progress); +extern void WaitForLockersMultiple(List *locktags, LOCKMODE lockmode, bool progress); /* Lock an XID for tuple insertion (used to wait for an insertion to finish) */ extern uint32 SpeculativeInsertionLockAcquire(TransactionId xid); diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index 16b927cb801..e117b391774 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -544,7 +544,7 @@ extern bool LockHeldByMe(const LOCKTAG *locktag, LOCKMODE lockmode); extern bool LockHasWaiters(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock); extern VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag, - LOCKMODE lockmode); + LOCKMODE lockmode, int *countp); extern void AtPrepare_Locks(void); extern void PostPrepare_Locks(TransactionId xid); extern int LockCheckConflicts(LockMethod lockMethodTable, -- 2.17.1