Repository: StarRocks/starrocks
Pull request #57969: [BugFix] Fix the issue of MV refresh failure
Author: login: zhaohehuhu
Labels: name: 3.4-merged
name: 3.5-merged
Created: 2025-04-14T05:34:12Z | Merged: 2025-04-18T05:20:42Z

## Why I'm doing:
I encountered a ConcurrentModificationException during materialized view (MV) refresh. The code attempts to ​​remove entries from a Map while iterating over it using Stream.forEach()​​, which leads to a ​​ConcurrentModificationException.
```
java.util.ConcurrentModificationException
	at java.base/java.util.HashMap$EntrySpliterator.tryAdvance(HashMap.java:1787)
	at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:127)
	at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:502)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:488)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
	at com.starrocks.scheduler.mv.MVPCTRefreshRangePartitioner.filterPartitionsByTTL(MVPCTRefreshRangePartitioner.java:364)
	at com.starrocks.scheduler.mv.MVPCTRefreshRangePartitioner.syncAddOrDropPartitions(MVPCTRefreshRangePartitioner.java:122)
	at com.starrocks.scheduler.PartitionBasedMvRefreshProcessor.syncPartitions(PartitionBasedMvRefreshProcessor.java:959)
	at com.starrocks.scheduler.PartitionBasedMvRefreshProcessor.doRefreshMaterializedView(PartitionBasedMvRefreshProcessor.java:422)
	at com.starrocks.scheduler.PartitionBasedMvRefreshProcessor.doRefreshMaterializedViewWithRetry(PartitionBasedMvRefreshProcessor.java:377)
	at com.starrocks.scheduler.PartitionBasedMvRefreshProcessor.doMvRefresh(PartitionBasedMvRefreshProcessor.java:336)
	at com.starrocks.scheduler.PartitionBasedMvRefreshProcessor.processTaskRun(PartitionBasedMvRefreshProcessor.java:205)
	at com.starrocks.scheduler.TaskRun.executeTaskRun(TaskRun.java:272)
	at com.starrocks.scheduler.TaskRunExecutor.lambda$executeTaskRun$0(TaskRunExecutor.java:59)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:829)

```
## What I'm doing:
as title

## 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
- [x] This pr needs user documentation (for new or modified features or behaviors)
  - [ ] I have added documentation for my new feature or new function
- [x] 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
  - [x] 3.4
  - [ ] 3.3
  - [ ] 3.2
  - [ ] 3.1



Diff:
diff --git a/fe/fe-core/src/main/java/com/starrocks/scheduler/mv/MVPCTRefreshRangePartitioner.java b/fe/fe-core/src/main/java/com/starrocks/scheduler/mv/MVPCTRefreshRangePartitioner.java
index 65ee4356266c10..c31e7dc611f6c5 100644
--- a/fe/fe-core/src/main/java/com/starrocks/scheduler/mv/MVPCTRefreshRangePartitioner.java
+++ b/fe/fe-core/src/main/java/com/starrocks/scheduler/mv/MVPCTRefreshRangePartitioner.java
@@ -343,9 +343,10 @@ protected void filterPartitionsByTTL(Map<String, PCell> toRefreshPartitions,
         if (partitionTTLNumber > 0 && toRefreshPartitionNum > partitionTTLNumber) {
             // remove the oldest partitions
             int toRemoveNum = toRefreshPartitionNum - partitionTTLNumber;
-            toRefreshPartitions.entrySet().stream()
+            List<String> keysToRemove = toRefreshPartitions.keySet().stream()
                     .limit(toRemoveNum)
-                    .forEach(e -> toRefreshPartitions.remove(e.getKey()));
+                    .collect(Collectors.toList());
+            keysToRemove.forEach(toRefreshPartitions::remove);
         }
     }
 
diff --git a/fe/fe-core/src/test/java/com/starrocks/scheduler/mv/MVPCTRefreshRangePartitionerTest.java b/fe/fe-core/src/test/java/com/starrocks/scheduler/mv/MVPCTRefreshRangePartitionerTest.java
new file mode 100644
index 00000000000000..9573681b796a5f
--- /dev/null
+++ b/fe/fe-core/src/test/java/com/starrocks/scheduler/mv/MVPCTRefreshRangePartitionerTest.java
@@ -0,0 +1,54 @@
+// Copyright 2021-present StarRocks, Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.starrocks.scheduler.mv;
+
+import com.google.common.collect.Maps;
+import com.starrocks.catalog.MaterializedView;
+import com.starrocks.catalog.PartitionInfo;
+import com.starrocks.catalog.TableProperty;
+import com.starrocks.scheduler.MvTaskRunContext;
+import com.starrocks.sql.common.PCell;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MVPCTRefreshRangePartitionerTest {
+
+    @Test
+    public void testFilterPartitionsByTTL() {
+        MvTaskRunContext mvContext = mock(MvTaskRunContext.class);
+        when(mvContext.getPartitionTTLNumber()).thenReturn(2);
+
+        MaterializedView mv = mock(MaterializedView.class);
+        when(mv.getTableProperty()).thenReturn(mock(TableProperty.class));
+        when(mv.getPartitionInfo()).thenReturn(mock(PartitionInfo.class));
+        when(mv.getTableProperty().getPartitionTTLNumber()).thenReturn(2);
+
+        MVPCTRefreshRangePartitioner partitioner = new MVPCTRefreshRangePartitioner(mvContext, null, null, mv);
+
+        Map<String, PCell> toRefreshPartitions = Maps.newHashMap();
+        toRefreshPartitions.put("partition1", mock(PCell.class));
+        toRefreshPartitions.put("partition2", mock(PCell.class));
+        toRefreshPartitions.put("partition3", mock(PCell.class));
+
+        partitioner.filterPartitionsByTTL(toRefreshPartitions, true);
+
+        Assert.assertEquals(2, toRefreshPartitions.size());
+    }
+}
\ No newline at end of file


Reviews:
user: login: LiShuMing
body: +1
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T06:42:11Z
user: login: LiShuMing
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T06:51:17Z
user: login: zhaohehuhu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T06:55:31Z
user: login: LiShuMing
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T10:56:42Z
user: login: satanson
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T10:58:08Z

Review comments:
diff_hunk: @@ -343,9 +343,10 @@ protected void filterPartitionsByTTL(Map<String, PCell> toRefreshPartitions,
         if (partitionTTLNumber > 0 && toRefreshPartitionNum > partitionTTLNumber) {
             // remove the oldest partitions
             int toRemoveNum = toRefreshPartitionNum - partitionTTLNumber;
-            toRefreshPartitions.entrySet().stream()
+            List<String> keysToRemove = toRefreshPartitions.keySet().stream()
                     .limit(toRemoveNum)
-                    .forEach(e -> toRefreshPartitions.remove(e.getKey()));
+                    .collect(Collectors.toList());
+            keysToRemove.forEach(toRefreshPartitions::remove);
path: fe/fe-core/src/main/java/com/starrocks/scheduler/mv/MVPCTRefreshRangePartitioner.java
user: login: LiShuMing
body: LGTM. Can you add some tests to protect this?(wonder why there is no tests to cover this).
created_at: 2025-04-16T06:51:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -343,9 +343,10 @@ protected void filterPartitionsByTTL(Map<String, PCell> toRefreshPartitions,
         if (partitionTTLNumber > 0 && toRefreshPartitionNum > partitionTTLNumber) {
             // remove the oldest partitions
             int toRemoveNum = toRefreshPartitionNum - partitionTTLNumber;
-            toRefreshPartitions.entrySet().stream()
+            List<String> keysToRemove = toRefreshPartitions.keySet().stream()
                     .limit(toRemoveNum)
-                    .forEach(e -> toRefreshPartitions.remove(e.getKey()));
+                    .collect(Collectors.toList());
+            keysToRemove.forEach(toRefreshPartitions::remove);
path: fe/fe-core/src/main/java/com/starrocks/scheduler/mv/MVPCTRefreshRangePartitioner.java
user: login: zhaohehuhu
body: Yup. I'm trying to add some UTs.
created_at: 2025-04-16T06:55:31Z
author_association: CONTRIBUTOR

PR comments:
user: login: zhaohehuhu
created_at: 2025-04-16T05:50:48Z
body: @Youngwb  plz help review it
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-04-16T09:19:38Z
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=57969) **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=57969) (required ≥ A)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=StarRocks_starrocks&pullRequest=57969)

##   
![](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-04-17T13:07:02Z
body: #### [Java-Extensions Incremental Coverage Report]


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




author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-17T13:07:54Z
body: #### [FE Incremental Coverage Report]

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




#### file detail

|   |path|covered_line|new_line|coverage|not_covered_line_detail|
|----|----|----|----|----|----|
|:large_blue_circle:|com/starrocks/scheduler/mv/MVPCTRefreshRangePartitioner.java|3|3|100.00%|[]|




author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-17T13:08:25Z
body: #### [BE Incremental Coverage Report]


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




author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-18T05:20:55Z
body: @Mergifyio backport branch-3.5
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-18T05:20:55Z
body: @Mergifyio backport branch-3.4
author_association: CONTRIBUTOR
user: login: mergify[bot]
created_at: 2025-04-18T05:21:19Z
body: > backport branch-3.5

#### ✅ Backports have been created

<details>

* [#58170 [BugFix] Fix the issue of MV refresh failure (backport #57969)](https://github.com/StarRocks/starrocks/pull/58170) 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: mergify[bot]
created_at: 2025-04-18T05:21:20Z
body: > backport branch-3.4

#### ✅ Backports have been created

<details>

* [#58171 [BugFix] Fix the issue of MV refresh failure (backport #57969)](https://github.com/StarRocks/starrocks/pull/58171) has been created for branch `branch-3.4`

</details>


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