From 37dc7e74afa8d73e3164a7d8d85f7f06fed3a09a Mon Sep 17 00:00:00 2001 From: Roman Eskin Date: Fri, 20 Feb 2026 09:23:28 +1000 Subject: [PATCH 1/2] Use CTAS approach for rebalancing the materialized view Problem description: Before this patch, in order to rebalance a materialized view, 2 steps were required: the actual rebalance where distribution policy was updated, and the refresh step to update the data in the materialized view. This approach had 2 problems with respect to usage in 'ggrebalance' tool for cluster shrink: 1. It could change the actual data in the materialized view before the cluster shrink, and after the shrink, if the view was not up-to-date. We intend to keep the logical data in the cluster not altered. 2. If a materialized view depends on another materialized view, there could be a race condition when doing the refresh, when we try to refresh based on the yet-not-refreshed one. Fix: Use the CTAS approach from the EXPAND TABLE specifically when we are rebalancing a materialized view. It creates a temp table with a correct distribution policy, where all data from the materialized view is copied, and then the relfilenode of the materialized view is swapped with the temp table. It keeps the data as it was before the rebalance, even if it was not up-to-date (therefore we will not surprise the user with the not expected view content), and it eliminates dependencies on other objects besides the materialized view itself. --- src/backend/commands/tablecmds.c | 29 +++++++++++++------ src/test/regress/expected/alter_rebalance.out | 4 --- src/test/regress/sql/alter_rebalance.sql | 2 -- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 224b1c235c22..e4d7707b496f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -513,7 +513,9 @@ static void ATExecRebalanceTable(List **wqueue, Relation rel, AlterTableCmd *cmd static void ATRepackTable(Relation origTable, AlteredTableInfo *tab); static void ATExecExpandPartitionTablePrepare(Relation rel); -static void ATExecExpandTableCTAS(AlterTableCmd *rootCmd, Relation rel, AlterTableCmd *cmd); +static void ATExecRebalanceTableCTAS(AlterTableCmd *rootCmd, + Relation rel, AlterTableCmd *cmd, + int targetNumSegments); static void ATExecSetDistributedBy(Relation rel, Node *node, AlterTableCmd *cmd); @@ -17513,7 +17515,7 @@ ATExecExpandTable(List **wqueue, Relation rel, AlterTableCmd *cmd) } else { - ATExecExpandTableCTAS(rootCmd, rel, cmd); + ATExecRebalanceTableCTAS(rootCmd, rel, cmd, 0); } /* Update numsegments to cluster size */ @@ -17651,11 +17653,6 @@ ATExecRebalanceTable(List **wqueue, Relation rel, AlterTableCmd *cmd) * child partitions. */ } - else if (rel->rd_rel->relkind == RELKIND_MATVIEW) - { - ereport((Gp_role == GP_ROLE_EXECUTE) ? DEBUG1 : NOTICE, - (errmsg("Materialized view requires REFRESH after rebalance"))); - } else if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) { if (rel_is_external_table(relid)) @@ -17677,6 +17674,18 @@ ATExecRebalanceTable(List **wqueue, Relation rel, AlterTableCmd *cmd) return; } } + else if (rel->rd_rel->relkind == RELKIND_MATVIEW) + { + /* + * We can't insert data directly to an existing matview, + * therefore the approach from ATExecShrinkTable() is not suitable, + * and we use CTAS method for matviews. + */ + AlteredTableInfo *tab = linitial(*wqueue); + AlterTableCmd *rootCmd = + (AlterTableCmd *)linitial(tab->subcmds[AT_PASS_MISC]); + ATExecRebalanceTableCTAS(rootCmd, rel, cmd, targetNumSegments); + } else { ATExecShrinkTable(rel, newPolicy); @@ -17778,7 +17787,8 @@ ATExecExpandPartitionTablePrepare(Relation rel) } static void -ATExecExpandTableCTAS(AlterTableCmd *rootCmd, Relation rel, AlterTableCmd *cmd) +ATExecRebalanceTableCTAS(AlterTableCmd *rootCmd, Relation rel, + AlterTableCmd *cmd, int targetNumSegments) { RangeVar *tmprv; Oid tmprelid; @@ -17817,7 +17827,8 @@ ATExecExpandTableCTAS(AlterTableCmd *rootCmd, Relation rel, AlterTableCmd *cmd) /* Step (b) - build CTAS */ distby = make_distributedby_for_rel(rel); - distby->numsegments = getgpsegmentCount(); + distby->numsegments = + (targetNumSegments > 0) ? targetNumSegments : getgpsegmentCount(); queryDesc = build_ctas_with_dist(rel, distby, untransformRelOptions(get_rel_opts(rel)), diff --git a/src/test/regress/expected/alter_rebalance.out b/src/test/regress/expected/alter_rebalance.out index 0f1e88d1d6ee..24a6a1c892a2 100644 --- a/src/test/regress/expected/alter_rebalance.out +++ b/src/test/regress/expected/alter_rebalance.out @@ -3561,8 +3561,6 @@ insert into test_table select generate_series(1, 10); create materialized view mv_test_table as select a from test_table distributed by (a); alter table test_table rebalance 1; alter materialized view mv_test_table rebalance 1; -NOTICE: Materialized view requires REFRESH after rebalance -refresh materialized view mv_test_table; select count(1), gp_segment_id from test_table group by gp_segment_id; count | gp_segment_id -------+--------------- @@ -3600,8 +3598,6 @@ select count(1), gp_segment_id from mv_test_table group by gp_segment_id order b begin; alter table test_table rebalance 1; alter materialized view mv_test_table rebalance 1; -NOTICE: Materialized view requires REFRESH after rebalance -refresh materialized view mv_test_table; rollback; select count(1), gp_segment_id from test_table group by gp_segment_id order by gp_segment_id; count | gp_segment_id diff --git a/src/test/regress/sql/alter_rebalance.sql b/src/test/regress/sql/alter_rebalance.sql index f11c3b3ddb19..09c9555f5b06 100644 --- a/src/test/regress/sql/alter_rebalance.sql +++ b/src/test/regress/sql/alter_rebalance.sql @@ -463,7 +463,6 @@ create materialized view mv_test_table as select a from test_table distributed b alter table test_table rebalance 1; alter materialized view mv_test_table rebalance 1; -refresh materialized view mv_test_table; select count(1), gp_segment_id from test_table group by gp_segment_id; select count(1), gp_segment_id from mv_test_table group by gp_segment_id; @@ -483,7 +482,6 @@ select count(1), gp_segment_id from mv_test_table group by gp_segment_id order b begin; alter table test_table rebalance 1; alter materialized view mv_test_table rebalance 1; -refresh materialized view mv_test_table; rollback; select count(1), gp_segment_id from test_table group by gp_segment_id order by gp_segment_id; From 6766ff070344276f101c37fbf01b523e2d7a8d40 Mon Sep 17 00:00:00 2001 From: Roman Eskin Date: Tue, 24 Feb 2026 19:27:34 +1000 Subject: [PATCH 2/2] Update solution --- src/backend/commands/tablecmds.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e4d7707b496f..f4c4412e640a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -17515,7 +17515,7 @@ ATExecExpandTable(List **wqueue, Relation rel, AlterTableCmd *cmd) } else { - ATExecRebalanceTableCTAS(rootCmd, rel, cmd, 0); + ATExecRebalanceTableCTAS(rootCmd, rel, cmd, getgpsegmentCount()); } /* Update numsegments to cluster size */ @@ -17827,8 +17827,7 @@ ATExecRebalanceTableCTAS(AlterTableCmd *rootCmd, Relation rel, /* Step (b) - build CTAS */ distby = make_distributedby_for_rel(rel); - distby->numsegments = - (targetNumSegments > 0) ? targetNumSegments : getgpsegmentCount(); + distby->numsegments = targetNumSegments; queryDesc = build_ctas_with_dist(rel, distby, untransformRelOptions(get_rel_opts(rel)),