Hi, while testing int->bigint migrations for our db at work I really missed ALTER TABLE progress reporting and during the waits I checked the code. It seems to me that ALTERs can be mostly categorized as 1) trivial ones - metadata rewrites, fast adding/removing columns, trying to change column type to one already present etc. not much to report here 2) scanning ones - adding constraints - it imho gives enough info to report blocks total and scanned and tuples scanned 3) rewrites - actually changing data or types - add number of written blocks/tuples 3b) index rewrites - report number of indexes processed
>From that it seems to me that the basic info is very similar to already present CLUSTER/VACUUM-FULL reporting so I tried to tap into that and just add a support for a new command. I identified a handful of places where to add the reporting for ALTERs and it seems to work, What I changed: `commands/progress.h` - new cluster reporting command + new phase for FK checks `commands/tablecmds.c` - start and end reporting inside `ATRewriteTables()` - report blocks total, blocks and tuples scanned and possibly tuples written in `ATRewriteTable` - add at least phase info in `validateForeignKeyConstraint`, possibly more if the check cannot be done by left join `catalog/system_views.sql` - output for the new command and phase `catalog/storage.c` - number of blocks processed in `RelationCopyStorage()` for the case table is moved between tablespaces by direct copying + some basic documentation updates What I did not have to change - index rebuilds used by CLUSTER reported their progress already, it just was not shown without a valid command configured. I ran some manual tests locally + ran regression tests and it seems to work fine. The reporting may be a bit crude and may be missing some phases but it covers the IO-heavy operations with some reasonable numbers. (well, not the FK check by left anti-join, but I don't want to mess with that + maybe number of FKs checked might be shown?) Thanks Best regards jkavalik
From 12cfbbc3448237733193fbed8a4383ba8656237e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Kaval=C3=ADk?= <jkava...@gmail.com> Date: Sun, 25 May 2025 23:23:56 +0200 Subject: [PATCH] ALTER TABLE progress support --- doc/src/sgml/monitoring.sgml | 13 +++---- doc/src/sgml/ref/alter_table.sgml | 10 ++++++ src/backend/catalog/storage.c | 7 ++++ src/backend/catalog/system_views.sql | 2 ++ src/backend/commands/tablecmds.c | 54 ++++++++++++++++++++++++++++ src/include/commands/progress.h | 2 ++ src/test/regress/expected/rules.out | 2 ++ 7 files changed, 84 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 4265a22d4de..09307c5f490 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -400,7 +400,7 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser <row> <entry><structname>pg_stat_progress_cluster</structname><indexterm><primary>pg_stat_progress_cluster</primary></indexterm></entry> <entry>One row for each backend running - <command>CLUSTER</command> or <command>VACUUM FULL</command>, showing current progress. + <command>CLUSTER</command>, <command>VACUUM FULL</command> or <command>ALTER TABLE</command>, showing current progress. See <xref linkend="cluster-progress-reporting"/>. </entry> </row> @@ -5492,7 +5492,7 @@ FROM pg_stat_get_backend_idset() AS backendid; <productname>PostgreSQL</productname> has the ability to report the progress of certain commands during command execution. Currently, the only commands which support progress reporting are <command>ANALYZE</command>, - <command>CLUSTER</command>, + <command>CLUSTER</command>, <command>ALTER TABLE</command>, <command>CREATE INDEX</command>, <command>VACUUM</command>, <command>COPY</command>, and <xref linkend="protocol-replication-base-backup"/> (i.e., replication @@ -5738,8 +5738,9 @@ FROM pg_stat_get_backend_idset() AS backendid; </indexterm> <para> - Whenever <command>CLUSTER</command> or <command>VACUUM FULL</command> is - running, the <structname>pg_stat_progress_cluster</structname> view will + Whenever <command>CLUSTER</command>, <command>VACUUM FULL</command> + or <command>ALTER TABLE</command> is running, + the <structname>pg_stat_progress_cluster</structname> view will contain a row for each backend that is currently running either command. The tables below describe the information that will be reported and provide information about how to interpret it. @@ -5801,7 +5802,7 @@ FROM pg_stat_get_backend_idset() AS backendid; <structfield>command</structfield> <type>text</type> </para> <para> - The command that is running. Either <literal>CLUSTER</literal> or <literal>VACUUM FULL</literal>. + The command that is running. Either <literal>CLUSTER</literal>, <literal>VACUUM FULL</literal> or <literal>ALTER TABLE</literal>. </para></entry> </row> @@ -5884,7 +5885,7 @@ FROM pg_stat_get_backend_idset() AS backendid; </table> <table id="cluster-phases"> - <title>CLUSTER and VACUUM FULL Phases</title> + <title>CLUSTER, VACUUM FULL and ALTER TABLE Phases</title> <tgroup cols="2"> <colspec colname="col1" colwidth="1*"/> <colspec colname="col2" colwidth="2*"/> diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index d63f3a621ac..228f27ac5fc 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -1859,6 +1859,16 @@ ALTER TABLE measurement </para> </refsect1> + <refsect1> + <title>Progress Reporting</title> + <para> + When an <command>ALTER TABLE</command> operation rewrites the table, progress + can be monitored via the <literal>pg_stat_progress_cluster</literal> system view, + similar to <command>CLUSTER</command> and <command>VACUUM FULL</command> commands. + The command type will be reported as <literal>'ALTER TABLE'</literal>. + </para> + </refsect1> + <refsect1> <title>See Also</title> diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 227df90f89c..79d14d86bc9 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -26,6 +26,7 @@ #include "access/xlogutils.h" #include "catalog/storage.h" #include "catalog/storage_xlog.h" +#include "commands/progress.h" #include "miscadmin.h" #include "pgstat.h" #include "storage/bulk_write.h" @@ -505,6 +506,9 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst, nblocks = smgrnblocks(src, forkNum); + /* Report expected number of block to copy */ + pgstat_progress_update_param(PROGRESS_CLUSTER_TOTAL_HEAP_BLKS, nblocks); + for (blkno = 0; blkno < nblocks; blkno++) { BulkWriteBuffer buf; @@ -556,6 +560,9 @@ RelationCopyStorage(SMgrRelation src, SMgrRelation dst, * page including any unused space. */ smgr_bulk_write(bulkstate, blkno, buf, false); + + /* Update progress report */ + pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_BLKS_SCANNED, blkno + 1); } smgr_bulk_finish(bulkstate); } diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 08f780a2e63..99a6ee0060f 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1252,6 +1252,7 @@ CREATE VIEW pg_stat_progress_cluster AS S.relid AS relid, CASE S.param1 WHEN 1 THEN 'CLUSTER' WHEN 2 THEN 'VACUUM FULL' + WHEN 3 THEN 'ALTER TABLE' END AS command, CASE S.param2 WHEN 0 THEN 'initializing' WHEN 1 THEN 'seq scanning heap' @@ -1261,6 +1262,7 @@ CREATE VIEW pg_stat_progress_cluster AS WHEN 5 THEN 'swapping relation files' WHEN 6 THEN 'rebuilding index' WHEN 7 THEN 'performing final cleanup' + WHEN 8 THEN 'checking foreign key constraints' END AS phase, CAST(S.param3 AS oid) AS cluster_index_relid, S.param4 AS heap_tuples_scanned, diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index acf11e83c04..9ad05d39164 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -59,6 +59,7 @@ #include "commands/comment.h" #include "commands/defrem.h" #include "commands/event_trigger.h" +#include "commands/progress.h" #include "commands/sequence.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" @@ -5839,6 +5840,10 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, if (!RELKIND_HAS_STORAGE(tab->relkind)) continue; + /* Start progress reporting */ + pgstat_progress_start_command(PROGRESS_COMMAND_CLUSTER, tab->relid); + pgstat_progress_update_param(PROGRESS_CLUSTER_COMMAND, PROGRESS_CLUSTER_COMMAND_ALTER_TABLE); + /* * If we change column data types, the operation has to be propagated * to tables that use this table's rowtype as a column type. @@ -5979,6 +5984,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, */ ATRewriteTable(tab, OIDNewHeap); + /* Report that we are now swapping relation files */ + pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, + PROGRESS_CLUSTER_PHASE_SWAP_REL_FILES); /* * Swap the physical files of the old and new heaps, then rebuild * indexes and discard the old heap. We can use RecentXmin for @@ -6090,6 +6098,10 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, table_close(rel, NoLock); } + /* Report that we are now doing clean up */ + pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, + PROGRESS_CLUSTER_PHASE_FINAL_CLEANUP); + /* Finally, run any afterStmts that were queued up */ foreach(ltab, *wqueue) { @@ -6104,6 +6116,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, CommandCounterIncrement(); } } + + pgstat_progress_end_command(); } /* @@ -6129,6 +6143,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) BulkInsertState bistate; int ti_options; ExprState *partqualstate = NULL; + int numTuples = 0; /* * Open the relation(s). We have surely already locked the existing @@ -6138,6 +6153,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) oldTupDesc = tab->oldDesc; newTupDesc = RelationGetDescr(oldrel); /* includes all mods */ + /* Update progress reporting - we are actually scanning and possibly rewriting the table */ + pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, PROGRESS_CLUSTER_PHASE_SEQ_SCAN_HEAP); + pgstat_progress_update_param(PROGRESS_CLUSTER_TOTAL_HEAP_BLKS, RelationGetNumberOfBlocks(oldrel)); + if (OidIsValid(OIDNewHeap)) { Assert(CheckRelationOidLockedByMe(OIDNewHeap, AccessExclusiveLock, @@ -6245,6 +6264,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) TupleTableSlot *oldslot; TupleTableSlot *newslot; TableScanDesc scan; + HeapScanDesc heapScan; MemoryContext oldCxt; List *dropped_attrs = NIL; ListCell *lc; @@ -6344,6 +6364,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) */ snapshot = RegisterSnapshot(GetLatestSnapshot()); scan = table_beginscan(oldrel, snapshot, 0, NULL); + heapScan = (HeapScanDesc) scan; + pgstat_progress_update_param(PROGRESS_CLUSTER_TOTAL_HEAP_BLKS, + heapScan->rs_nblocks); /* * Switch to per-tuple memory context and reset it for each tuple @@ -6354,6 +6377,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) while (table_scan_getnextslot(scan, ForwardScanDirection, oldslot)) { TupleTableSlot *insertslot; + pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_BLKS_SCANNED, + (heapScan->rs_cblock + + heapScan->rs_nblocks - + heapScan->rs_startblock + ) % heapScan->rs_nblocks + 1); + pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_TUPLES_SCANNED, + ++numTuples); if (tab->rewrite > 0) { @@ -6402,6 +6432,9 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) ExecStoreVirtualTuple(newslot); + pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_TUPLES_WRITTEN, + numTuples); + /* * Now, evaluate any expressions whose inputs come from the * new tuple. We assume these columns won't reference each @@ -6522,6 +6555,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) CHECK_FOR_INTERRUPTS(); } + pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_BLKS_SCANNED, + heapScan->rs_nblocks); MemoryContextSwitchTo(oldCxt); table_endscan(scan); @@ -13638,10 +13673,12 @@ validateForeignKeyConstraint(char *conname, { TupleTableSlot *slot; TableScanDesc scan; + HeapScanDesc heapScan; Trigger trig = {0}; Snapshot snapshot; MemoryContext oldcxt; MemoryContext perTupCxt; + int numTuples = 0; ereport(DEBUG1, (errmsg_internal("validating foreign key constraint \"%s\"", conname))); @@ -13660,6 +13697,10 @@ validateForeignKeyConstraint(char *conname, trig.tginitdeferred = false; /* we needn't fill in remaining fields */ + /* Report that we are now checking foreign keys */ + pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, + PROGRESS_CLUSTER_PHASE_CHECK_FKEYS); + /* * See if we can do it with a single LEFT JOIN query. A false result * indicates we must proceed with the fire-the-trigger method. We can't do @@ -13677,6 +13718,7 @@ validateForeignKeyConstraint(char *conname, snapshot = RegisterSnapshot(GetLatestSnapshot()); slot = table_slot_create(rel, NULL); scan = table_beginscan(rel, snapshot, 0, NULL); + heapScan = (HeapScanDesc) scan; perTupCxt = AllocSetContextCreate(CurrentMemoryContext, "validateForeignKeyConstraint", @@ -13712,6 +13754,14 @@ validateForeignKeyConstraint(char *conname, RI_FKey_check_ins(fcinfo); MemoryContextReset(perTupCxt); + + pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_BLKS_SCANNED, + (heapScan->rs_cblock + + heapScan->rs_nblocks - + heapScan->rs_startblock + ) % heapScan->rs_nblocks + 1); + pgstat_progress_update_param(PROGRESS_CLUSTER_HEAP_TUPLES_SCANNED, + ++numTuples); } MemoryContextSwitchTo(oldcxt); @@ -16776,6 +16826,10 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) */ rel = relation_open(tableOid, lockmode); + /* Update progress reporting - we are copying the table */ + pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, PROGRESS_CLUSTER_PHASE_WRITE_NEW_HEAP); + pgstat_progress_update_param(PROGRESS_CLUSTER_TOTAL_HEAP_BLKS, RelationGetNumberOfBlocks(rel)); + /* Check first if relation can be moved to new tablespace */ if (!CheckRelationTableSpaceMove(rel, newTableSpace)) { diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h index 7c736e7b03b..56585742838 100644 --- a/src/include/commands/progress.h +++ b/src/include/commands/progress.h @@ -74,10 +74,12 @@ #define PROGRESS_CLUSTER_PHASE_SWAP_REL_FILES 5 #define PROGRESS_CLUSTER_PHASE_REBUILD_INDEX 6 #define PROGRESS_CLUSTER_PHASE_FINAL_CLEANUP 7 +#define PROGRESS_CLUSTER_PHASE_CHECK_FKEYS 8 /* Commands of PROGRESS_CLUSTER */ #define PROGRESS_CLUSTER_COMMAND_CLUSTER 1 #define PROGRESS_CLUSTER_COMMAND_VACUUM_FULL 2 +#define PROGRESS_CLUSTER_COMMAND_ALTER_TABLE 3 /* New command type */ /* Progress parameters for CREATE INDEX */ /* 3, 4 and 5 reserved for "waitfor" metrics */ diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 6cf828ca8d0..3b8a0ec0756 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1982,6 +1982,7 @@ pg_stat_progress_cluster| SELECT s.pid, CASE s.param1 WHEN 1 THEN 'CLUSTER'::text WHEN 2 THEN 'VACUUM FULL'::text + WHEN 3 THEN 'ALTER TABLE'::text ELSE NULL::text END AS command, CASE s.param2 @@ -1993,6 +1994,7 @@ pg_stat_progress_cluster| SELECT s.pid, WHEN 5 THEN 'swapping relation files'::text WHEN 6 THEN 'rebuilding index'::text WHEN 7 THEN 'performing final cleanup'::text + WHEN 8 THEN 'checking foreign key constraints'::text ELSE NULL::text END AS phase, (s.param3)::oid AS cluster_index_relid, -- 2.34.1