From cafaf3870a3e0fde517473aa45459d13a23ddc4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Kaval=C3=ADk?= 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 | 53 ++++++++++++++++++++++++++++ src/include/commands/progress.h | 2 ++ src/test/regress/expected/rules.out | 2 ++ 7 files changed, 83 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 7b9fa20df9e5..e85e562399cb 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 pg_stat_progress_clusterpg_stat_progress_cluster One row for each backend running - CLUSTER or VACUUM FULL, showing current progress. + CLUSTER, VACUUM FULL or ALTER TABLE, showing current progress. See . @@ -5586,7 +5586,7 @@ FROM pg_stat_get_backend_idset() AS backendid; PostgreSQL has the ability to report the progress of certain commands during command execution. Currently, the only commands which support progress reporting are ANALYZE, - CLUSTER, + CLUSTER, ALTER TABLE, CREATE INDEX, VACUUM, COPY, and (i.e., replication @@ -5832,8 +5832,9 @@ FROM pg_stat_get_backend_idset() AS backendid; - Whenever CLUSTER or VACUUM FULL is - running, the pg_stat_progress_cluster view will + Whenever CLUSTER, VACUUM FULL + or ALTER TABLE is running, + the pg_stat_progress_cluster 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. @@ -5895,7 +5896,7 @@ FROM pg_stat_get_backend_idset() AS backendid; command text - The command that is running. Either CLUSTER or VACUUM FULL. + The command that is running. Either CLUSTER, VACUUM FULL or ALTER TABLE. @@ -5978,7 +5979,7 @@ FROM pg_stat_get_backend_idset() AS backendid; - CLUSTER and VACUUM FULL Phases + CLUSTER, VACUUM FULL and ALTER TABLE Phases diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 9d23ad5a0fb4..7b1e71ddb173 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -1869,6 +1869,16 @@ ALTER TABLE measurement + + Progress Reporting + + When an ALTER TABLE operation rewrites the table, progress + can be monitored via the pg_stat_progress_cluster system view, + similar to CLUSTER and VACUUM FULL commands. + The command type will be reported as 'ALTER TABLE'. + + + See Also diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index c58e9418ac31..162f5310d621 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 059e8778ca7c..59f158c71f25 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1277,6 +1277,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' @@ -1286,6 +1287,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 23ebaa3f2300..885bc658d39b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -60,6 +60,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" @@ -5841,6 +5842,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. @@ -5981,6 +5986,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 @@ -6092,6 +6100,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) { @@ -6106,6 +6118,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, CommandCounterIncrement(); } } + + pgstat_progress_end_command(); } /* @@ -6131,6 +6145,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) BulkInsertState bistate; int ti_options; ExprState *partqualstate = NULL; + int64 numTuples = 0; /* * Open the relation(s). We have surely already locked the existing @@ -6140,6 +6155,9 @@ 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); + if (OidIsValid(OIDNewHeap)) { Assert(CheckRelationOidLockedByMe(OIDNewHeap, AccessExclusiveLock, @@ -6247,6 +6265,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap) TupleTableSlot *oldslot; TupleTableSlot *newslot; TableScanDesc scan; + HeapScanDesc heapScan; MemoryContext oldCxt; List *dropped_attrs = NIL; ListCell *lc; @@ -6346,6 +6365,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 @@ -6356,6 +6378,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) { @@ -6404,6 +6433,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 @@ -6524,6 +6556,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); @@ -13692,10 +13726,12 @@ validateForeignKeyConstraint(char *conname, { TupleTableSlot *slot; TableScanDesc scan; + HeapScanDesc heapScan; Trigger trig = {0}; Snapshot snapshot; MemoryContext oldcxt; MemoryContext perTupCxt; + int64 numTuples = 0; ereport(DEBUG1, (errmsg_internal("validating foreign key constraint \"%s\"", conname))); @@ -13714,6 +13750,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 @@ -13731,6 +13771,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", @@ -13766,6 +13807,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); @@ -16856,6 +16905,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 1cde4bd9bcf1..613005ae641d 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 7c52181cbcb3..0673ae361fcb 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2001,6 +2001,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 @@ -2012,6 +2013,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,