Repository: StarRocks/starrocks
Pull request #58990: [BugFix] Fail to build MaterializedView relationship when do reload after FE image loaded
Author: login: wxl24life
Labels: name: 3.5-merged
Created: 2025-05-15T04:03:43Z | Merged: 2025-05-15T11:07:31Z

## Why I'm doing:

## What I'm doing:

Fixes #issue

## What type of PR is this:

- [x] BugFix
- [ ] Feature
- [ ] Enhancement
- [ ] Refactor
- [ ] UT
- [ ] Doc
- [ ] Tool

Does this PR entail a change in behavior?

- [ ] Yes, this PR will result in a change in behavior.
- [x] No, this PR will not result in a change in behavior.

If yes, please specify the type of change:

- [ ] Interface/UI changes: syntax, type conversion, expression evaluation, display information
- [ ] Parameter changes: default values, similar parameters but with different default values
- [ ] Policy changes: use new policy to replace old one, functionality automatically enabled
- [ ] Feature removed
- [ ] Miscellaneous: upgrade & downgrade compatibility, etc.

## Checklist:

- [x] I have added test cases for my bug fix or my new feature
- [ ] This pr needs user documentation (for new or modified features or behaviors)
  - [ ] I have added documentation for my new feature or new function
- [ ] This is a backport pr

## Bugfix cherry-pick branch check:
- [x] I have checked the version labels which the pr will be auto-backported to the target branch
  - [x] 3.5
  - [ ] 3.4
  - [ ] 3.3
  - [ ] 3.2
  - [ ] 3.1



Diff:
diff --git a/fe/fe-core/src/main/java/com/starrocks/catalog/MaterializedView.java b/fe/fe-core/src/main/java/com/starrocks/catalog/MaterializedView.java
index 4cd73651cff8d4..7fc8a1c128bc71 100644
--- a/fe/fe-core/src/main/java/com/starrocks/catalog/MaterializedView.java
+++ b/fe/fe-core/src/main/java/com/starrocks/catalog/MaterializedView.java
@@ -39,6 +39,7 @@
 import com.starrocks.catalog.constraint.ForeignKeyConstraint;
 import com.starrocks.catalog.constraint.GlobalConstraintManager;
 import com.starrocks.catalog.mv.MVPlanValidationResult;
+import com.starrocks.common.Config;
 import com.starrocks.common.DdlException;
 import com.starrocks.common.FeConstants;
 import com.starrocks.common.MaterializedViewExceptions;
@@ -737,7 +738,7 @@ public void setQueryOutputIndices(List<Integer> queryOutputIndices) {
         this.queryOutputIndices = queryOutputIndices;
     }
 
-    public boolean isReloaded() {
+    public boolean hasReloaded() {
         return reloaded;
     }
 
@@ -1069,9 +1070,6 @@ public void onReload(boolean postLoadImage) {
             LOG.error("reload mv failed: {}", this, e);
             setInactiveAndReason("reload failed: " + e.getMessage());
         }
-        if (postLoadImage) {
-            reloaded = true;
-        }
     }
 
     /**
@@ -1148,12 +1146,19 @@ private boolean onReloadImpl(boolean postLoadImage) {
                 continue;
             } else if (table.isMaterializedView()) {
                 MaterializedView baseMV = (MaterializedView) table;
-                // skip reloading only when postLoadImage is true
-                if (postLoadImage && baseMV.isReloaded()) {
-                    continue;
+                // only consider skipping reload when postLoadImage is true
+                if (Config.enable_mv_post_image_reload_cache && postLoadImage) {
+                    if (!baseMV.hasReloaded()) {
+                        // recursive reload MV, to guarantee the order of hierarchical MV
+                        baseMV.onReload(postLoadImage);
+                        baseMV.setReloaded(true);
+                    } else {
+                        LOG.info("baseMv: {} has reloaded before, skip reload it again", baseMV.getName());
+                    }
+                } else {
+                    // recursive reload MV, to guarantee the order of hierarchical MV
+                    baseMV.onReload(postLoadImage);
                 }
-                // recursive reload MV, to guarantee the order of hierarchical MV
-                baseMV.onReload(postLoadImage);
                 if (!baseMV.isActive()) {
                     LOG.warn("tableName :{} is invalid. set materialized view:{} to invalid",
                             baseTableInfo.getTableName(), id);
diff --git a/fe/fe-core/src/main/java/com/starrocks/common/Config.java b/fe/fe-core/src/main/java/com/starrocks/common/Config.java
index fcfc86ef38eb6c..9146bac05ac007 100644
--- a/fe/fe-core/src/main/java/com/starrocks/common/Config.java
+++ b/fe/fe-core/src/main/java/com/starrocks/common/Config.java
@@ -3303,6 +3303,10 @@ public class Config extends ConfigBase {
             "query the rewritten mv directly rather than original base table to improve query performance.")
     public static boolean enable_mv_refresh_query_rewrite = false;
 
+    @ConfField(mutable = true, comment = "Whether do reload flag check after FE's image loaded." +
+            " If one base mv has done reload, no need to do it again while other mv that related to it is reloading ")
+    public static boolean enable_mv_post_image_reload_cache = true;
+
     /**
      * Whether analyze the mv after refresh in async mode.
      */
diff --git a/fe/fe-core/src/test/java/com/starrocks/catalog/MaterializedViewTest.java b/fe/fe-core/src/test/java/com/starrocks/catalog/MaterializedViewTest.java
index 7b58238cd2462b..861905371ac747 100644
--- a/fe/fe-core/src/test/java/com/starrocks/catalog/MaterializedViewTest.java
+++ b/fe/fe-core/src/test/java/com/starrocks/catalog/MaterializedViewTest.java
@@ -895,7 +895,7 @@ public void testCreateMVWithCoolDownTime() throws Exception {
     }
 
     @Test
-    public void testMaterializedViewReload() throws Exception {
+    public void testMaterializedViewReloadNotPostLoadImage() throws Exception {
         starRocksAssert.withDatabase("test").useDatabase("test")
                 .withTable("CREATE TABLE base_table\n" +
                         "(\n" +
@@ -926,6 +926,7 @@ public void testMaterializedViewReload() throws Exception {
                         "REFRESH manual\n" +
                         "as select k1,k2,v1 from base_mv;");
         Database testDb = GlobalStateMgr.getCurrentState().getLocalMetastore().getDb("test");
+        Table baseTable = GlobalStateMgr.getCurrentState().getLocalMetastore().getTable(testDb.getFullName(), "base_table");
         MaterializedView baseMv = ((MaterializedView) GlobalStateMgr.getCurrentState().getLocalMetastore()
                 .getTable(testDb.getFullName(), "base_mv"));
         MaterializedView mv1 = ((MaterializedView) GlobalStateMgr.getCurrentState().getLocalMetastore()
@@ -934,26 +935,123 @@ public void testMaterializedViewReload() throws Exception {
                 .getTable(testDb.getFullName(), "mv2"));
 
         {
-            // before post image reload, all materialized views has `reloaded` flag set to false
-            Assert.assertFalse(mv1.isReloaded());
-            Assert.assertFalse(mv2.isReloaded());
-            Assert.assertFalse(baseMv.isReloaded());
+            Config.enable_mv_post_image_reload_cache = false;
+            boolean postLoadImage = false;
 
-            GlobalStateMgr.getCurrentState().processMvRelatedMeta();
+            baseMv.setReloaded(false);
+            baseTable.removeRelatedMaterializedView(baseMv.getMvId());
+            baseMv.removeRelatedMaterializedView(mv1.getMvId());
+            baseMv.removeRelatedMaterializedView(mv2.getMvId());
 
-            // after post image reload, all materialized views should have `reloaded` flag reset to false
-            Assert.assertFalse(mv1.isReloaded());
-            Assert.assertFalse(mv2.isReloaded());
-            Assert.assertFalse(baseMv.isReloaded());
+            mv1.onReload(postLoadImage);
+            mv2.onReload(postLoadImage);
+
+            Assert.assertFalse(baseMv.hasReloaded());
+            Assert.assertEquals(1, baseTable.getRelatedMaterializedViews().size());
+            Assert.assertEquals(2, baseMv.getRelatedMaterializedViews().size());
+            Assert.assertTrue(baseMv.getRelatedMaterializedViews().contains(mv1.getMvId()));
+            Assert.assertTrue(baseMv.getRelatedMaterializedViews().contains(mv2.getMvId()));
         }
 
         {
+            Config.enable_mv_post_image_reload_cache = true;
             boolean postLoadImage = true;
+
+            baseMv.setReloaded(false);
+            baseTable.removeRelatedMaterializedView(baseMv.getMvId());
+            baseMv.removeRelatedMaterializedView(mv1.getMvId());
+            baseMv.removeRelatedMaterializedView(mv2.getMvId());
+
+            mv1.onReload(postLoadImage);
+            mv2.onReload(postLoadImage);
+
+            Assert.assertTrue(baseMv.hasReloaded());
+            Assert.assertEquals(1, baseTable.getRelatedMaterializedViews().size());
+            Assert.assertEquals(2, baseMv.getRelatedMaterializedViews().size());
+            Assert.assertTrue(baseMv.getRelatedMaterializedViews().contains(mv1.getMvId()));
+            Assert.assertTrue(baseMv.getRelatedMaterializedViews().contains(mv2.getMvId()));
+        }
+
+        {
+            Config.enable_mv_post_image_reload_cache = true;
+            boolean postLoadImage = false;
+
+            baseMv.setReloaded(false);
+            baseTable.removeRelatedMaterializedView(baseMv.getMvId());
+            baseMv.removeRelatedMaterializedView(mv1.getMvId());
+            baseMv.removeRelatedMaterializedView(mv2.getMvId());
+
             mv1.onReload(postLoadImage);
             mv2.onReload(postLoadImage);
-            Assert.assertTrue(mv1.isReloaded());
-            Assert.assertTrue(mv2.isReloaded());
-            Assert.assertTrue(baseMv.isReloaded());
+
+            Assert.assertFalse(baseMv.hasReloaded());
+            Assert.assertEquals(1, baseTable.getRelatedMaterializedViews().size());
+            Assert.assertEquals(2, baseMv.getRelatedMaterializedViews().size());
+            Assert.assertTrue(baseMv.getRelatedMaterializedViews().contains(mv1.getMvId()));
+            Assert.assertTrue(baseMv.getRelatedMaterializedViews().contains(mv2.getMvId()));
         }
     }
+
+
+    @Test
+    public void testMaterializedViewReloadPostLoadImage() throws Exception {
+        starRocksAssert.withDatabase("test").useDatabase("test")
+                .withTable("CREATE TABLE base_table\n" +
+                        "(\n" +
+                        "    k1 date,\n" +
+                        "    k2 int,\n" +
+                        "    v1 int sum\n" +
+                        ")\n" +
+                        "PARTITION BY RANGE(k1)\n" +
+                        "(\n" +
+                        "    PARTITION p1 values [('2022-02-01'),('2022-02-16')),\n" +
+                        "    PARTITION p2 values [('2022-02-16'),('2022-03-01'))\n" +
+                        ")\n" +
+                        "DISTRIBUTED BY HASH(k2) BUCKETS 3\n" +
+                        "PROPERTIES('replication_num' = '1');")
+                .withMaterializedView("CREATE MATERIALIZED VIEW base_mv\n" +
+                        "PARTITION BY k1\n" +
+                        "DISTRIBUTED BY HASH(k2) BUCKETS 3\n" +
+                        "REFRESH manual\n" +
+                        "as select k1,k2,v1 from base_table;")
+                .withMaterializedView("CREATE MATERIALIZED VIEW mv1\n" +
+                        "PARTITION BY k1\n" +
+                        "DISTRIBUTED BY HASH(k2) BUCKETS 3\n" +
+                        "REFRESH manual\n" +
+                        "as select k1,k2,v1 from base_mv;")
+                .withMaterializedView("CREATE MATERIALIZED VIEW mv2\n" +
+                        "PARTITION BY k1\n" +
+                        "DISTRIBUTED BY HASH(k2) BUCKETS 3\n" +
+                        "REFRESH manual\n" +
+                        "as select k1,k2,v1 from base_mv;");
+        Database testDb = GlobalStateMgr.getCurrentState().getLocalMetastore().getDb("test");
+        Table baseTable = GlobalStateMgr.getCurrentState().getLocalMetastore().getTable(testDb.getFullName(), "base_table");
+        MaterializedView baseMv = ((MaterializedView) GlobalStateMgr.getCurrentState().getLocalMetastore()
+                .getTable(testDb.getFullName(), "base_mv"));
+        MaterializedView mv1 = ((MaterializedView) GlobalStateMgr.getCurrentState().getLocalMetastore()
+                .getTable(testDb.getFullName(), "mv1"));
+        MaterializedView mv2 = ((MaterializedView) GlobalStateMgr.getCurrentState().getLocalMetastore()
+                .getTable(testDb.getFullName(), "mv2"));
+
+        baseTable.removeRelatedMaterializedView(baseMv.getMvId());
+        baseMv.removeRelatedMaterializedView(mv1.getMvId());
+        baseMv.removeRelatedMaterializedView(mv2.getMvId());
+
+        Assert.assertFalse(mv1.hasReloaded());
+        Assert.assertFalse(mv2.hasReloaded());
+        Assert.assertFalse(baseMv.hasReloaded());
+
+        Config.enable_mv_post_image_reload_cache = true;
+        // do post image reload
+        GlobalStateMgr.getCurrentState().processMvRelatedMeta();
+
+        // after post image reload, all materialized views should have `reloaded` flag reset to false
+        Assert.assertFalse(mv1.hasReloaded());
+        Assert.assertFalse(mv2.hasReloaded());
+        Assert.assertFalse(baseMv.hasReloaded());
+        Assert.assertEquals(1, baseTable.getRelatedMaterializedViews().size());
+        Assert.assertEquals(2, baseMv.getRelatedMaterializedViews().size());
+        Assert.assertTrue(baseMv.getRelatedMaterializedViews().contains(mv1.getMvId()));
+        Assert.assertTrue(baseMv.getRelatedMaterializedViews().contains(mv2.getMvId()));
+    }
 }


Reviews:
user: login: LiShuMing
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T06:45:18Z
user: login: wxl24life
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T07:31:31Z
user: login: LiShuMing
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T10:07:52Z
user: login: gengjun-git
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T10:12:01Z

Review comments:
diff_hunk: @@ -3303,6 +3303,9 @@ public class Config extends ConfigBase {
             "query the rewritten mv directly rather than original base table to improve query performance.")
     public static boolean enable_mv_refresh_query_rewrite = false;
 
+    @ConfField(mutable = true, comment = "Whether do reload flag check after FE's image loaded.")
+    public static boolean enable_mv_reload_check_post_image = false;
path: fe/fe-core/src/main/java/com/starrocks/common/Config.java
user: login: LiShuMing
body: 1. `true` by default is ok, otherwise it can not be used?
2. Add docs into `FE Configurations`?
created_at: 2025-05-15T06:38:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3303,6 +3303,9 @@ public class Config extends ConfigBase {
             "query the rewritten mv directly rather than original base table to improve query performance.")
     public static boolean enable_mv_refresh_query_rewrite = false;
 
+    @ConfField(mutable = true, comment = "Whether do reload flag check after FE's image loaded.")
+    public static boolean enable_mv_reload_check_post_image = false;
path: fe/fe-core/src/main/java/com/starrocks/common/Config.java
user: login: LiShuMing
body: 3. This cannot be used for `mutable`?
created_at: 2025-05-15T06:40:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3303,6 +3303,9 @@ public class Config extends ConfigBase {
             "query the rewritten mv directly rather than original base table to improve query performance.")
     public static boolean enable_mv_refresh_query_rewrite = false;
 
+    @ConfField(mutable = true, comment = "Whether do reload flag check after FE's image loaded.")
+    public static boolean enable_mv_reload_check_post_image = false;
path: fe/fe-core/src/main/java/com/starrocks/common/Config.java
user: login: LiShuMing
body: 4. `enable_mv_post_image_reload_cache` is a better name, and this only affects for nested mv.
created_at: 2025-05-15T06:43:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1148,12 +1146,17 @@ private boolean onReloadImpl(boolean postLoadImage) {
                 continue;
             } else if (table.isMaterializedView()) {
                 MaterializedView baseMV = (MaterializedView) table;
-                // skip reloading only when postLoadImage is true
-                if (postLoadImage && baseMV.isReloaded()) {
-                    continue;
+                // only consider skipping reload when postLoadImage is true
+                if (Config.enable_mv_reload_check_post_image && postLoadImage) {
+                    if (!baseMV.isReloaded()) {
+                        // recursive reload MV, to guarantee the order of hierarchical MV
path: fe/fe-core/src/main/java/com/starrocks/catalog/MaterializedView.java
user: login: LiShuMing
body: 1. if `baseMV.isReloaded` is true, add a `logInfo` to trace it?
2. `hasReloaded` seems a better name?
created_at: 2025-05-15T06:45:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3303,6 +3303,9 @@ public class Config extends ConfigBase {
             "query the rewritten mv directly rather than original base table to improve query performance.")
     public static boolean enable_mv_refresh_query_rewrite = false;
 
+    @ConfField(mutable = true, comment = "Whether do reload flag check after FE's image loaded.")
+    public static boolean enable_mv_reload_check_post_image = false;
path: fe/fe-core/src/main/java/com/starrocks/common/Config.java
user: login: wxl24life
body: > 3. This cannot be used for `mutable`?

`mutable` is fine here. fe's Checkpoint and Replayer thread will do post load image action repeatedly
created_at: 2025-05-15T07:31:31Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-05-15T07:59:28Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=StarRocks_starrocks&pullRequest=58990) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [B Maintainability Rating on New Code](https://sonarcloud.io/dashboard?id=StarRocks_starrocks&pullRequest=58990) (required ≥ A)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=StarRocks_starrocks&pullRequest=58990)

##   
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension ![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png '') [SonarQube for IDE](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-15T09:59:58Z
body: #### [Java-Extensions Incremental Coverage Report]


:white_check_mark: **pass** : 0 / 0 (0%)




author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-15T10:00:33Z
body: #### [FE Incremental Coverage Report]

:white_check_mark: **pass** : 7 / 7 (100.00%)




#### file detail

|   |path|covered_line|new_line|coverage|not_covered_line_detail|
|----|----|----|----|----|----|
|:large_blue_circle:|com/starrocks/catalog/MaterializedView.java|6|6|100.00%|[]|
|:large_blue_circle:|com/starrocks/common/Config.java|1|1|100.00%|[]|




author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-15T10:01:16Z
body: #### [BE Incremental Coverage Report]


:white_check_mark: **pass** : 0 / 0 (0%)




author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-15T11:07:43Z
body: @Mergifyio backport branch-3.5
author_association: CONTRIBUTOR
user: login: mergify[bot]
created_at: 2025-05-15T11:08:12Z
body: > backport branch-3.5

#### ✅ Backports have been created

<details>

* [#59008 [BugFix] Fail to build MaterializedView relationship when do reload after FE image loaded (backport #58990)](https://github.com/StarRocks/starrocks/pull/59008) has been created for branch `branch-3.5`

</details>


<!---
DO NOT EDIT
-*- Mergify Payload -*-
{"command": "backport branch-3.5", "conclusion": "success", "action_is_running": true}
-*- Mergify Payload End -*-
-->
author_association: CONTRIBUTOR
user: login: andyziye
created_at: 2025-05-15T12:43:02Z
body: @mergifyio backport branch-3.5.0-rc01
author_association: COLLABORATOR
user: login: mergify[bot]
created_at: 2025-05-15T12:43:08Z
body: > backport branch-3.5.0-rc01

#### ✅ Backports have been created

<details>

* [#59014 [BugFix] Fail to build MaterializedView relationship when do reload after FE image loaded (backport #58990)](https://github.com/StarRocks/starrocks/pull/59014) has been created for branch `branch-3.5.0-rc01`

</details>


<!---
DO NOT EDIT
-*- Mergify Payload -*-
{"command": "backport branch-3.5.0-rc01", "conclusion": "success", "action_is_running": true}
-*- Mergify Payload End -*-
-->
author_association: CONTRIBUTOR