From e1dd29dd801ab453209c193354cfc77d60bb27f5 Mon Sep 17 00:00:00 2001 From: zclllyybb Date: Wed, 14 Jan 2026 21:49:29 +0800 Subject: [PATCH] [Fix](partition) fix concurrent visit of partition items (#59848) Related PR: introduced by https://github.com/apache/doris/pull/56921 Problem Summary: fix concurrency bug when dynamic partition visit partition items at the same time with modifying partitions: ```java 2026-01-12 15:31:46,108 ERROR (thrift-server-pool-31|451) [ProcessFunction.process():47] Internal error processing createPartition java.lang.reflect.UndeclaredThrowableException at jdk.proxy2/jdk.proxy2.$Proxy27.createPartition(Unknown Source) at org.apache.doris.thrift.FrontendService$Processor$createPartition.getResult(FrontendService.java:4993) at org.apache.doris.thrift.FrontendService$Processor$createPartition.getResult(FrontendService.java:4973) at org.apache.thrift.ProcessFunction.process(ProcessFunction.java:38) at org.apache.thrift.TBaseProcessor.process(TBaseProcessor.java:38) at org.apache.thrift.server.TThreadPoolServer$WorkerProcess.run(TThreadPoolServer.java:250) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:833) Caused by: java.lang.reflect.InvocationTargetException at jdk.internal.reflect.GeneratedMethodAccessor27.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.apache.doris.service.FeServer.lambda$start$0(FeServer.java:60) ... 9 more Caused by: java.util.ConcurrentModificationException at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1597) at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1630) at java.base/java.util.HashMap$EntryIterator.next(HashMap.java:1628) at java.base/java.util.AbstractCollection.toArray(AbstractCollection.java:146) at java.base/java.util.ArrayList.(ArrayList.java:181) at org.apache.doris.clone.DynamicPartitionScheduler.getHistoricalPartitions(DynamicPartitionScheduler.java:209) at org.apache.doris.common.util.AutoBucketCalculator.calculateAutoBuckets(AutoBucketCalculator.java:142) at org.apache.doris.common.util.AutoBucketCalculator.calculateAutoBucketsWithBoundsCheck(AutoBucketCalculator.java:190) at org.apache.doris.analysis.PartitionExprUtil.getAddPartitionClauseFromPartitionValues(PartitionExprUtil.java:202) at org.apache.doris.service.FrontendServiceImpl.createPartition(FrontendServiceImpl.java:3622) ... 13 more ``` all these visit operations should under table's lock. --- .../org/apache/doris/catalog/OlapTable.java | 2 +- .../apache/doris/catalog/PartitionInfo.java | 4 +++- .../clone/DynamicPartitionScheduler.java | 19 ++++++++++++------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java index 0ac334fd2b3eea..4aac2062ee3f97 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java @@ -181,7 +181,7 @@ public enum OlapTableState { private KeysType keysType; @Setter @SerializedName(value = "pi", alternate = {"partitionInfo"}) - private PartitionInfo partitionInfo; + private PartitionInfo partitionInfo; // should modify only under table's lock @SerializedName(value = "itp", alternate = {"idToPartition"}) @Getter protected ConcurrentHashMap idToPartition = new ConcurrentHashMap<>(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionInfo.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionInfo.java index 6b00bd39fcacfe..89e6c637a3a134 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionInfo.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionInfo.java @@ -46,7 +46,7 @@ import java.util.stream.Collectors; /* - * Repository of a partition's related infos + * Repository of a partition's related infos. should modify only under table's lock. */ public class PartitionInfo { private static final Logger LOG = LogManager.getLogger(PartitionInfo.class); @@ -137,6 +137,7 @@ public String getDisplayPartitionColumns() { return sb.toString(); } + // need read lock of table public Map getIdToItem(boolean isTemp) { if (isTemp) { return idToTempItem; @@ -196,6 +197,7 @@ private void setItemInternal(long partitionId, boolean isTemp, PartitionItem ite } } + // need write lock of table public PartitionItem handleNewSinglePartitionDesc(SinglePartitionDesc desc, long partitionId, boolean isTemp) throws DdlException { Preconditions.checkArgument(desc.isAnalyzed()); diff --git a/fe/fe-core/src/main/java/org/apache/doris/clone/DynamicPartitionScheduler.java b/fe/fe-core/src/main/java/org/apache/doris/clone/DynamicPartitionScheduler.java index 7f93e1660d063e..de63ce1745c33d 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/clone/DynamicPartitionScheduler.java +++ b/fe/fe-core/src/main/java/org/apache/doris/clone/DynamicPartitionScheduler.java @@ -205,13 +205,18 @@ private static Pair getBucketsNum(DynamicPartitionProperty pro } public static List getHistoricalPartitions(OlapTable table, String nowPartitionName) { - RangePartitionInfo info = (RangePartitionInfo) (table.getPartitionInfo()); - List> idToItems = new ArrayList<>(info.getIdToItem(false).entrySet()); - idToItems.sort(Comparator.comparing(o -> ((RangePartitionItem) o.getValue()).getItems().upperEndpoint())); - return idToItems.stream() - .map(entry -> table.getPartition(entry.getKey())) - .filter(partition -> partition != null && !partition.getName().equals(nowPartitionName)) - .collect(Collectors.toList()); + table.readLock(); + try { + RangePartitionInfo info = (RangePartitionInfo) (table.getPartitionInfo()); + List> idToItems = new ArrayList<>(info.getIdToItem(false).entrySet()); + idToItems.sort(Comparator.comparing(o -> ((RangePartitionItem) o.getValue()).getItems().upperEndpoint())); + return idToItems.stream() + .map(entry -> table.getPartition(entry.getKey())) + .filter(partition -> partition != null && !partition.getName().equals(nowPartitionName)) + .collect(Collectors.toList()); + } finally { + table.readUnlock(); + } } public static List filterDataPartitions(List partitions, List visibleVersions) {