This is an automated email from the ASF dual-hosted git repository.

zclllyybb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new bd16e5d68fd [fix](fe) Guard auto partition result against concurrent 
drops (#65282)
bd16e5d68fd is described below

commit bd16e5d68fd280343ccf6da61b2d47a9d3c2615d
Author: zclllyybb <[email protected]>
AuthorDate: Thu Jul 23 16:45:17 2026 +0800

    [fix](fe) Guard auto partition result against concurrent drops (#65282)
    
    Problem Summary: Auto partition creation can race with dynamic partition
    retention. After new partitions are added, the response builder reads
    the table partition map and partition info without holding the table
    metadata lock. If a retention drop removes one of those partitions in
    the same window, the response builder can observe a missing partition or
    partition item and hit a null pointer while constructing partition and
    tablet metadata. This change snapshots the required partition metadata
    under the table read lock, returns a normal error when the partition was
    already dropped before the snapshot, and keeps tablet
    location/cache/backend resolution work outside the table lock.
---
 .../apache/doris/service/FrontendServiceImpl.java  | 438 ++++++++++++---------
 .../doris/service/FrontendServiceImplTest.java     |  49 +++
 2 files changed, 303 insertions(+), 184 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java 
b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
index b737ed2bc94..6a5695cebb0 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/service/FrontendServiceImpl.java
@@ -40,6 +40,7 @@ import org.apache.doris.catalog.MaterializedIndex;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.Partition;
 import org.apache.doris.catalog.PartitionInfo;
+import org.apache.doris.catalog.PartitionItem;
 import org.apache.doris.catalog.PartitionType;
 import org.apache.doris.catalog.Replica;
 import org.apache.doris.catalog.Table;
@@ -4702,26 +4703,6 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
             }
         }
 
-        // check partition's number limit. because partitions in 
addPartitionClauseMap may be duplicated with existing
-        // partitions, which would lead to false positive. so we should check 
the partition number AFTER adding new
-        // partitions using its ACTUAL NUMBER, rather than the sum of existing 
and requested partitions.
-        int partitionNum = olapTable.getPartitionNum();
-        int autoPartitionLimit = Config.max_auto_partition_num;
-        if (partitionNum > autoPartitionLimit) {
-            String errorMessage = String.format(
-                    "partition numbers %d exceeded limit of variable 
max_auto_partition_num %d",
-                    partitionNum, autoPartitionLimit);
-            LOG.warn(errorMessage);
-            errorStatus.setErrorMsgs(Lists.newArrayList(errorMessage));
-            result.setStatus(errorStatus);
-            LOG.warn("send create partition error status: {}", result);
-            return result;
-        } else if (partitionNum > autoPartitionLimit * 8 / 10) {
-            LOG.warn("Table {}.{} auto partition count {} is approaching limit 
{} (>80%)."
-                        + " Consider increasing max_auto_partition_num.",
-                    db.getFullName(), olapTable.getName(), partitionNum, 
autoPartitionLimit);
-        }
-
         // build partition & tablets
         List<TTabletLocation> tablets = new ArrayList<>();
         List<TTabletLocation> slaveTablets = new ArrayList<>();
@@ -4733,41 +4714,59 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
                 && request.isEnableAdaptiveRandomBucket();
         boolean loadToSingleTablet = request.isSetLoadToSingleTablet() && 
request.isLoadToSingleTablet();
         final boolean hasBeEndpoint = request.isSetBeEndpoint();
-        // Lazy: resolved on the first CloudTablet that needs it (skipped on 
cache-hit).
-        String cachedClusterId = null;
-        for (String partitionName : addPartitionClauseMap.keySet()) {
-            Partition partition = table.getPartition(partitionName);
-            // For thread safety, we preserve the tablet distribution 
information of each partition
-            // before calling getOrSetAutoPartitionInfo, but not check the 
partition first
-            List<TTabletLocation> partitionTablets = new ArrayList<>();
-            List<TTabletLocation> partitionSlaveTablets = new ArrayList<>();
-            TOlapTablePartition tPartition = new TOlapTablePartition();
-            tPartition.setId(partition.getId());
-            int partColNum = partitionInfo.getPartitionColumns().size();
+        List<PartitionResultSnapshot> partitionSnapshots = new ArrayList<>();
+
+        olapTable.readLock();
+        try {
+            // check partition's number limit. because partitions in 
addPartitionClauseMap may be duplicated with
+            // existing partitions, which would lead to false positive. so we 
should check the partition number AFTER
+            // adding new partitions using its ACTUAL NUMBER, rather than the 
sum of existing and requested partitions.
+            int partitionNum = olapTable.getPartitionNum();
+            int autoPartitionLimit = Config.max_auto_partition_num;
+            if (partitionNum > autoPartitionLimit) {
+                String errorMessage = String.format(
+                        "partition numbers %d exceeded limit of variable 
max_auto_partition_num %d",
+                        partitionNum, autoPartitionLimit);
+                LOG.warn(errorMessage);
+                errorStatus.setErrorMsgs(Lists.newArrayList(errorMessage));
+                result.setStatus(errorStatus);
+                LOG.warn("send create partition error status: {}", result);
+                return result;
+            } else if (partitionNum > autoPartitionLimit * 8 / 10) {
+                LOG.warn("Table {}.{} auto partition count {} is approaching 
limit {} (>80%)."
+                            + " Consider increasing max_auto_partition_num.",
+                        db.getFullName(), olapTable.getName(), partitionNum, 
autoPartitionLimit);
+            }
+
             try {
-                OlapTableSink.setPartitionKeys(tPartition, 
partitionInfo.getItem(partition.getId()), partColNum);
+                
partitionSnapshots.addAll(snapshotPartitionResultsByName(olapTable, 
addPartitionClauseMap.keySet(),
+                        loadToSingleTablet, enableAdaptiveRandomBucket, "auto 
partition"));
             } catch (UserException ex) {
                 errorStatus.setErrorMsgs(Lists.newArrayList(ex.getMessage()));
                 result.setStatus(errorStatus);
                 LOG.warn("send create partition error status: {}", result);
                 return result;
             }
-            for (MaterializedIndex index : 
partition.getMaterializedIndices(MaterializedIndex.IndexExtState.ALL)) {
-                tPartition.addToIndexes(new 
TOlapTableIndexTablets(index.getId(), Lists.newArrayList(
-                        
index.getTablets().stream().map(Tablet::getId).collect(Collectors.toList()))));
-                tPartition.setNumBuckets(index.getTablets().size());
-            }
-            
tPartition.setIsMutable(olapTable.getPartitionInfo().getIsMutable(partition.getId()));
-            boolean randomDistribution =
-                    partition.getDistributionInfo().getType() == 
DistributionInfo.DistributionInfoType.RANDOM;
-            boolean cacheLoadTabletIdx =
-                    (loadToSingleTablet || enableAdaptiveRandomBucket) && 
randomDistribution;
+        } finally {
+            olapTable.readUnlock();
+        }
+
+        // Lazy: resolved on the first CloudTablet that needs it (skipped on 
cache-hit).
+        String cachedClusterId = null;
+        for (PartitionResultSnapshot partitionSnapshot : partitionSnapshots) {
+            Partition partition = partitionSnapshot.partition;
+            long partitionId = partitionSnapshot.partitionId;
+            TOlapTablePartition tPartition = partitionSnapshot.tPartition;
+            boolean cacheLoadTabletIdx = partitionSnapshot.cacheLoadTabletIdx;
             partitions.add(tPartition);
-            // tablet
+            // For thread safety, we preserve the tablet distribution 
information of each partition
+            // before calling getOrSetAutoPartitionInfo, but not check the 
partition first
+            List<TTabletLocation> partitionTablets = new ArrayList<>();
+            List<TTabletLocation> partitionSlaveTablets = new ArrayList<>();
             AtomicLong cachedLoadTabletIdx = new AtomicLong(-1);
             if (needUseCache
                     && 
Env.getCurrentGlobalTransactionMgr().getAutoPartitionCacheMgr()
-                            .getAutoPartitionInfo(txnId, partition.getId(), 
partitionTablets,
+                            .getAutoPartitionInfo(txnId, partitionId, 
partitionTablets,
                                     partitionSlaveTablets, 
cachedLoadTabletIdx)) {
                 if (cacheLoadTabletIdx) {
                     tPartition.setLoadTabletIdx(cachedLoadTabletIdx.get());
@@ -4791,52 +4790,49 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
                     return result;
                 }
             }
-            int quorum = 
olapTable.getPartitionInfo().getReplicaAllocation(partition.getId()).getTotalReplicaNum()
 / 2
-                    + 1;
-            for (MaterializedIndex index : 
partition.getMaterializedIndices(MaterializedIndex.IndexExtState.ALL)) {
-                for (Tablet tablet : index.getTablets()) {
-                    // we should ensure the replica backend is alive
-                    // otherwise, there will be a 'unknown node id, id=xxx' 
error for stream load
-                    // BE id -> path hash
-                    Multimap<Long, Long> bePathsMap;
-                    try {
-                        if (tablet instanceof CloudTablet) {
-                            CloudTablet cloudTablet = (CloudTablet) tablet;
-                            if (hasBeEndpoint) {
-                                bePathsMap = 
cloudTablet.getNormalReplicaBackendPathMap(request.be_endpoint);
-                            } else {
-                                if (cachedClusterId == null) {
-                                    cachedClusterId = 
((CloudSystemInfoService) Env.getCurrentSystemInfo())
-                                            .getCurrentClusterId();
-                                }
-                                bePathsMap = 
cloudTablet.getNormalReplicaBackendPathMapByClusterId(cachedClusterId);
-                            }
+            int quorum = partitionSnapshot.quorum;
+            for (Tablet tablet : partitionSnapshot.tablets) {
+                // we should ensure the replica backend is alive
+                // otherwise, there will be a 'unknown node id, id=xxx' error 
for stream load
+                // BE id -> path hash
+                Multimap<Long, Long> bePathsMap;
+                try {
+                    if (tablet instanceof CloudTablet) {
+                        CloudTablet cloudTablet = (CloudTablet) tablet;
+                        if (hasBeEndpoint) {
+                            bePathsMap = 
cloudTablet.getNormalReplicaBackendPathMap(request.be_endpoint);
                         } else {
-                            bePathsMap = 
tablet.getNormalReplicaBackendPathMap();
+                            if (cachedClusterId == null) {
+                                cachedClusterId = ((CloudSystemInfoService) 
Env.getCurrentSystemInfo())
+                                        .getCurrentClusterId();
+                            }
+                            bePathsMap = 
cloudTablet.getNormalReplicaBackendPathMapByClusterId(cachedClusterId);
                         }
-                    } catch (UserException ex) {
-                        
errorStatus.setErrorMsgs(Lists.newArrayList(ex.getMessage()));
-                        result.setStatus(errorStatus);
-                        LOG.warn("send create partition error status: {}", 
result);
-                        return result;
-                    }
-                    if (bePathsMap.keySet().size() < quorum) {
-                        LOG.warn("auto go quorum exception");
-                    }
-                    if (request.isSetWriteSingleReplica() && 
request.isWriteSingleReplica()) {
-                        Long[] nodes = bePathsMap.keySet().toArray(new 
Long[0]);
-                        Random random = new SecureRandom();
-                        Long masterNode = nodes[random.nextInt(nodes.length)];
-                        Multimap<Long, Long> slaveBePathsMap = bePathsMap;
-                        slaveBePathsMap.removeAll(masterNode);
-                        partitionTablets.add(new 
TTabletLocation(tablet.getId(),
-                                
Lists.newArrayList(Sets.newHashSet(masterNode))));
-                        partitionSlaveTablets.add(new 
TTabletLocation(tablet.getId(),
-                                Lists.newArrayList(slaveBePathsMap.keySet())));
                     } else {
-                        partitionTablets.add(new 
TTabletLocation(tablet.getId(),
-                                Lists.newArrayList(bePathsMap.keySet())));
+                        bePathsMap = tablet.getNormalReplicaBackendPathMap();
                     }
+                } catch (UserException ex) {
+                    
errorStatus.setErrorMsgs(Lists.newArrayList(ex.getMessage()));
+                    result.setStatus(errorStatus);
+                    LOG.warn("send create partition error status: {}", result);
+                    return result;
+                }
+                if (bePathsMap.keySet().size() < quorum) {
+                    LOG.warn("auto go quorum exception");
+                }
+                if (request.isSetWriteSingleReplica() && 
request.isWriteSingleReplica()) {
+                    Long[] nodes = bePathsMap.keySet().toArray(new Long[0]);
+                    Random random = new SecureRandom();
+                    Long masterNode = nodes[random.nextInt(nodes.length)];
+                    Multimap<Long, Long> slaveBePathsMap = bePathsMap;
+                    slaveBePathsMap.removeAll(masterNode);
+                    partitionTablets.add(new TTabletLocation(tablet.getId(),
+                            Lists.newArrayList(Sets.newHashSet(masterNode))));
+                    partitionSlaveTablets.add(new 
TTabletLocation(tablet.getId(),
+                            Lists.newArrayList(slaveBePathsMap.keySet())));
+                } else {
+                    partitionTablets.add(new TTabletLocation(tablet.getId(),
+                            Lists.newArrayList(bePathsMap.keySet())));
                 }
             }
 
@@ -4864,7 +4860,7 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
             if (needUseCache) {
                 long loadTabletIdx = cacheLoadTabletIdx ? 
tPartition.getLoadTabletIdx() : -1;
                 long cachedTabletIdx = 
Env.getCurrentGlobalTransactionMgr().getAutoPartitionCacheMgr()
-                        .getOrSetAutoPartitionInfo(txnId, partition.getId(), 
partitionTablets,
+                        .getOrSetAutoPartitionInfo(txnId, partitionId, 
partitionTablets,
                                 partitionSlaveTablets, loadTabletIdx);
                 if (cacheLoadTabletIdx) {
                     tPartition.setLoadTabletIdx(cachedTabletIdx);
@@ -4987,6 +4983,14 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
             }
         }
 
+        Backend requestBackend = request.isSetBeEndpoint() ? 
resolveBeEndpoint(request.getBeEndpoint()) : null;
+        long adaptiveBucketBeId = requestBackend != null ? 
requestBackend.getId() : -1L;
+        TUniqueId queryId = request.isSetQueryId() ? request.getQueryId() : 
null;
+        boolean enableAdaptiveRandomBucket = 
request.isSetEnableAdaptiveRandomBucket()
+                && request.isEnableAdaptiveRandomBucket();
+        boolean loadToSingleTablet = request.isSetLoadToSingleTablet() && 
request.isLoadToSingleTablet();
+        final boolean replaceHasBeEndpoint = request.isSetBeEndpoint();
+
         InsertOverwriteManager overwriteManager = 
Env.getCurrentEnv().getInsertOverwriteManager();
         ReentrantLock taskLock = overwriteManager.getLock(taskGroupId);
         if (taskLock == null) {
@@ -5001,6 +5005,7 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
         ArrayList<Long> pendingPartitionIds = new ArrayList<>(); // pending: 
[1 2]
         ArrayList<Long> newPartitionIds = new ArrayList<>(); // requested temp 
partition ids. for [7 8]
         boolean needReplace = false;
+        List<PartitionResultSnapshot> partitionSnapshots = new ArrayList<>();
         try {
             taskLock.lock();
             // double check lock. maybe taskLock is not null, but has been 
removed from the Map. means the task failed.
@@ -5048,29 +5053,37 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
                     }
                 }
             }
-        } catch (DdlException | RuntimeException ex) {
-            errorStatus.setErrorMsgs(Lists.newArrayList(ex.getMessage()));
-            result.setStatus(errorStatus);
-            LOG.warn("send create partition error status: {}", result);
-            return result;
-        } finally {
-            taskLock.unlock();
-        }
 
-        // result: [1 2 5 6], make it [7 8 5 6]
-        int idx = 0;
-        if (needReplace) {
-            for (int i = 0; i < reqPartitionIds.size(); i++) {
-                if (reqPartitionIds.get(i).equals(resultPartitionIds.get(i))) {
-                    resultPartitionIds.set(i, newPartitionIds.get(idx++));
+            // result: [1 2 5 6], make it [7 8 5 6]
+            int idx = 0;
+            if (needReplace) {
+                for (int i = 0; i < reqPartitionIds.size(); i++) {
+                    if 
(reqPartitionIds.get(i).equals(resultPartitionIds.get(i))) {
+                        resultPartitionIds.set(i, newPartitionIds.get(idx++));
+                    }
                 }
             }
-        }
-        if (idx != newPartitionIds.size()) {
-            errorStatus.addToErrorMsgs("changed partition number " + idx + " 
is not correct");
+            if (idx != newPartitionIds.size()) {
+                errorStatus.addToErrorMsgs("changed partition number " + idx + 
" is not correct");
+                result.setStatus(errorStatus);
+                LOG.warn("send create partition error status: {}", result);
+                return result;
+            }
+
+            olapTable.readLock();
+            try {
+                
partitionSnapshots.addAll(snapshotPartitionResultsById(olapTable, 
resultPartitionIds,
+                        loadToSingleTablet, enableAdaptiveRandomBucket, 
"replace partition"));
+            } finally {
+                olapTable.readUnlock();
+            }
+        } catch (UserException | RuntimeException ex) {
+            errorStatus.setErrorMsgs(Lists.newArrayList(ex.getMessage()));
             result.setStatus(errorStatus);
             LOG.warn("send create partition error status: {}", result);
             return result;
+        } finally {
+            taskLock.unlock();
         }
 
         if (LOG.isDebugEnabled()) {
@@ -5087,51 +5100,23 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
         List<TOlapTablePartition> partitions = new ArrayList<>();
         List<TTabletLocation> tablets = new ArrayList<>();
         List<TTabletLocation> slaveTablets = new ArrayList<>();
-        PartitionInfo partitionInfo = olapTable.getPartitionInfo();
-        Backend requestBackend = request.isSetBeEndpoint() ? 
resolveBeEndpoint(request.getBeEndpoint()) : null;
-        long adaptiveBucketBeId = requestBackend != null ? 
requestBackend.getId() : -1L;
-        TUniqueId queryId = request.isSetQueryId() ? request.getQueryId() : 
null;
-        boolean enableAdaptiveRandomBucket = 
request.isSetEnableAdaptiveRandomBucket()
-                && request.isEnableAdaptiveRandomBucket();
-        boolean loadToSingleTablet = request.isSetLoadToSingleTablet() && 
request.isLoadToSingleTablet();
-        final boolean replaceHasBeEndpoint = request.isSetBeEndpoint();
         // Lazy: resolved on the first CloudTablet that needs it.
         String replaceCachedClusterId = null;
-        for (long partitionId : resultPartitionIds) {
-            Partition partition = olapTable.getPartition(partitionId);
+        for (PartitionResultSnapshot partitionSnapshot : partitionSnapshots) {
+            Partition partition = partitionSnapshot.partition;
+            long partitionId = partitionSnapshot.partitionId;
+            TOlapTablePartition tPartition = partitionSnapshot.tPartition;
+            boolean cacheLoadTabletIdx = partitionSnapshot.cacheLoadTabletIdx;
+            partitions.add(tPartition);
             // For thread safety, we preserve the tablet distribution 
information of each partition
             // before calling getOrSetAutoPartitionInfo, but not check the 
partition first
             List<TTabletLocation> partitionTablets = new ArrayList<>();
             List<TTabletLocation> partitionSlaveTablets = new ArrayList<>();
-            TOlapTablePartition tPartition = new TOlapTablePartition();
-            tPartition.setId(partition.getId());
-
-            // set partition keys
-            int partColNum = partitionInfo.getPartitionColumns().size();
-            try {
-                OlapTableSink.setPartitionKeys(tPartition, 
partitionInfo.getItem(partition.getId()), partColNum);
-            } catch (UserException ex) {
-                errorStatus.setErrorMsgs(Lists.newArrayList(ex.getMessage()));
-                result.setStatus(errorStatus);
-                LOG.warn("send replace partition error status: {}", result);
-                return result;
-            }
-            for (MaterializedIndex index : 
partition.getMaterializedIndices(MaterializedIndex.IndexExtState.ALL)) {
-                tPartition.addToIndexes(new 
TOlapTableIndexTablets(index.getId(), Lists.newArrayList(
-                        
index.getTablets().stream().map(Tablet::getId).collect(Collectors.toList()))));
-                tPartition.setNumBuckets(index.getTablets().size());
-            }
-            
tPartition.setIsMutable(olapTable.getPartitionInfo().getIsMutable(partition.getId()));
-            boolean randomDistribution =
-                    partition.getDistributionInfo().getType() == 
DistributionInfo.DistributionInfoType.RANDOM;
-            boolean cacheLoadTabletIdx =
-                    (loadToSingleTablet || enableAdaptiveRandomBucket) && 
randomDistribution;
-            partitions.add(tPartition);
             // tablet
             AtomicLong cachedLoadTabletIdx = new AtomicLong(-1);
             if (needUseCache && txnId != 0
                     && 
Env.getCurrentGlobalTransactionMgr().getAutoPartitionCacheMgr()
-                            .getAutoPartitionInfo(txnId, partition.getId(), 
partitionTablets,
+                            .getAutoPartitionInfo(txnId, partitionId, 
partitionTablets,
                                     partitionSlaveTablets, 
cachedLoadTabletIdx)) {
                 if (cacheLoadTabletIdx) {
                     tPartition.setLoadTabletIdx(cachedLoadTabletIdx.get());
@@ -5155,53 +5140,50 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
                     return result;
                 }
             }
-            int quorum = 
olapTable.getPartitionInfo().getReplicaAllocation(partition.getId()).getTotalReplicaNum()
 / 2
-                    + 1;
-            for (MaterializedIndex index : 
partition.getMaterializedIndices(MaterializedIndex.IndexExtState.ALL)) {
-                for (Tablet tablet : index.getTablets()) {
-                    // we should ensure the replica backend is alive
-                    // otherwise, there will be a 'unknown node id, id=xxx' 
error for stream load
-                    // BE id -> path hash
-                    Multimap<Long, Long> bePathsMap;
-                    try {
-                        if (tablet instanceof CloudTablet) {
-                            CloudTablet cloudTablet = (CloudTablet) tablet;
-                            if (replaceHasBeEndpoint) {
-                                bePathsMap = 
cloudTablet.getNormalReplicaBackendPathMap(request.be_endpoint);
-                            } else {
-                                if (replaceCachedClusterId == null) {
-                                    replaceCachedClusterId = 
((CloudSystemInfoService) Env.getCurrentSystemInfo())
-                                            .getCurrentClusterId();
-                                }
-                                bePathsMap = cloudTablet
-                                        
.getNormalReplicaBackendPathMapByClusterId(replaceCachedClusterId);
-                            }
+            int quorum = partitionSnapshot.quorum;
+            for (Tablet tablet : partitionSnapshot.tablets) {
+                // we should ensure the replica backend is alive
+                // otherwise, there will be a 'unknown node id, id=xxx' error 
for stream load
+                // BE id -> path hash
+                Multimap<Long, Long> bePathsMap;
+                try {
+                    if (tablet instanceof CloudTablet) {
+                        CloudTablet cloudTablet = (CloudTablet) tablet;
+                        if (replaceHasBeEndpoint) {
+                            bePathsMap = 
cloudTablet.getNormalReplicaBackendPathMap(request.be_endpoint);
                         } else {
-                            bePathsMap = 
tablet.getNormalReplicaBackendPathMap();
+                            if (replaceCachedClusterId == null) {
+                                replaceCachedClusterId = 
((CloudSystemInfoService) Env.getCurrentSystemInfo())
+                                        .getCurrentClusterId();
+                            }
+                            bePathsMap = cloudTablet
+                                    
.getNormalReplicaBackendPathMapByClusterId(replaceCachedClusterId);
                         }
-                    } catch (UserException ex) {
-                        
errorStatus.setErrorMsgs(Lists.newArrayList(ex.getMessage()));
-                        result.setStatus(errorStatus);
-                        LOG.warn("send replace partition error status: {}", 
result);
-                        return result;
-                    }
-                    if (bePathsMap.keySet().size() < quorum) {
-                        LOG.warn("auto go quorum exception");
-                    }
-                    if (request.isSetWriteSingleReplica() && 
request.isWriteSingleReplica()) {
-                        Long[] nodes = bePathsMap.keySet().toArray(new 
Long[0]);
-                        Random random = new SecureRandom();
-                        Long masterNode = nodes[random.nextInt(nodes.length)];
-                        Multimap<Long, Long> slaveBePathsMap = bePathsMap;
-                        slaveBePathsMap.removeAll(masterNode);
-                        partitionTablets.add(new 
TTabletLocation(tablet.getId(),
-                                
Lists.newArrayList(Sets.newHashSet(masterNode))));
-                        partitionSlaveTablets.add(new 
TTabletLocation(tablet.getId(),
-                                Lists.newArrayList(slaveBePathsMap.keySet())));
                     } else {
-                        partitionTablets.add(new 
TTabletLocation(tablet.getId(),
-                                Lists.newArrayList(bePathsMap.keySet())));
+                        bePathsMap = tablet.getNormalReplicaBackendPathMap();
                     }
+                } catch (UserException ex) {
+                    
errorStatus.setErrorMsgs(Lists.newArrayList(ex.getMessage()));
+                    result.setStatus(errorStatus);
+                    LOG.warn("send replace partition error status: {}", 
result);
+                    return result;
+                }
+                if (bePathsMap.keySet().size() < quorum) {
+                    LOG.warn("auto go quorum exception");
+                }
+                if (request.isSetWriteSingleReplica() && 
request.isWriteSingleReplica()) {
+                    Long[] nodes = bePathsMap.keySet().toArray(new Long[0]);
+                    Random random = new SecureRandom();
+                    Long masterNode = nodes[random.nextInt(nodes.length)];
+                    Multimap<Long, Long> slaveBePathsMap = bePathsMap;
+                    slaveBePathsMap.removeAll(masterNode);
+                    partitionTablets.add(new TTabletLocation(tablet.getId(),
+                            Lists.newArrayList(Sets.newHashSet(masterNode))));
+                    partitionSlaveTablets.add(new 
TTabletLocation(tablet.getId(),
+                            Lists.newArrayList(slaveBePathsMap.keySet())));
+                } else {
+                    partitionTablets.add(new TTabletLocation(tablet.getId(),
+                            Lists.newArrayList(bePathsMap.keySet())));
                 }
             }
 
@@ -5233,14 +5215,14 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
             if (needUseCache) {
                 long loadTabletIdx = cacheLoadTabletIdx ? 
tPartition.getLoadTabletIdx() : -1;
                 long cachedTabletIdx = 
Env.getCurrentGlobalTransactionMgr().getAutoPartitionCacheMgr()
-                        .getOrSetAutoPartitionInfo(txnId, partition.getId(), 
partitionTablets,
+                        .getOrSetAutoPartitionInfo(txnId, partitionId, 
partitionTablets,
                                 partitionSlaveTablets, loadTabletIdx);
                 if (cacheLoadTabletIdx) {
                     tPartition.setLoadTabletIdx(cachedTabletIdx);
                 }
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("Cache auto partition info, txnId: {}, 
partitionId: {}, "
-                            + "tablets: {}, slaveTablets: {}", txnId, 
partition.getId(),
+                            + "tablets: {}, slaveTablets: {}", txnId, 
partitionId,
                             partitionTablets.size(), 
partitionSlaveTablets.size());
                 }
             }
@@ -5272,6 +5254,94 @@ public class FrontendServiceImpl implements 
FrontendService.Iface {
         return result;
     }
 
+    private static final class PartitionResultSnapshot {
+        private final Partition partition;
+        private final long partitionId;
+        private final TOlapTablePartition tPartition;
+        private final List<Tablet> tablets;
+        private final int quorum;
+        private final boolean cacheLoadTabletIdx;
+
+        private PartitionResultSnapshot(Partition partition, long partitionId,
+                TOlapTablePartition tPartition, List<Tablet> tablets, int 
quorum, boolean cacheLoadTabletIdx) {
+            this.partition = partition;
+            this.partitionId = partitionId;
+            this.tPartition = tPartition;
+            this.tablets = tablets;
+            this.quorum = quorum;
+            this.cacheLoadTabletIdx = cacheLoadTabletIdx;
+        }
+    }
+
+    private static List<PartitionResultSnapshot> 
snapshotPartitionResultsByName(OlapTable olapTable,
+            Collection<String> partitionNames, boolean loadToSingleTablet, 
boolean enableAdaptiveRandomBucket,
+            String resultName) throws UserException {
+        PartitionInfo partitionInfo = olapTable.getPartitionInfo();
+        int partColNum = partitionInfo.getPartitionColumns().size();
+        List<PartitionResultSnapshot> partitionSnapshots = new ArrayList<>();
+        for (String partitionName : partitionNames) {
+            Partition partition = olapTable.getPartition(partitionName);
+            if (partition == null) {
+                throw new UserException(String.format(
+                        "partition %s was dropped concurrently while building 
%s result, please retry",
+                        partitionName, resultName));
+            }
+            partitionSnapshots.add(snapshotPartitionResult(partitionInfo, 
partColNum, partition, partitionName,
+                    loadToSingleTablet, enableAdaptiveRandomBucket, 
resultName));
+        }
+        return partitionSnapshots;
+    }
+
+    private static List<PartitionResultSnapshot> 
snapshotPartitionResultsById(OlapTable olapTable,
+            Collection<Long> partitionIds, boolean loadToSingleTablet, boolean 
enableAdaptiveRandomBucket,
+            String resultName) throws UserException {
+        PartitionInfo partitionInfo = olapTable.getPartitionInfo();
+        int partColNum = partitionInfo.getPartitionColumns().size();
+        List<PartitionResultSnapshot> partitionSnapshots = new ArrayList<>();
+        for (long partitionId : partitionIds) {
+            Partition partition = olapTable.getPartition(partitionId);
+            if (partition == null) {
+                throw new UserException(String.format(
+                        "partition %d was dropped concurrently while building 
%s result, please retry",
+                        partitionId, resultName));
+            }
+            partitionSnapshots.add(snapshotPartitionResult(partitionInfo, 
partColNum, partition,
+                    String.valueOf(partitionId), loadToSingleTablet, 
enableAdaptiveRandomBucket, resultName));
+        }
+        return partitionSnapshots;
+    }
+
+    private static PartitionResultSnapshot 
snapshotPartitionResult(PartitionInfo partitionInfo, int partColNum,
+            Partition partition, String partitionLabel, boolean 
loadToSingleTablet,
+            boolean enableAdaptiveRandomBucket, String resultName) throws 
UserException {
+        long partitionId = partition.getId();
+        PartitionItem partitionItem = partitionInfo.getItem(partitionId);
+        if (partitionItem == null) {
+            throw new UserException(String.format(
+                    "partition item of %s was dropped concurrently while 
building %s result, please retry",
+                    partitionLabel, resultName));
+        }
+
+        TOlapTablePartition tPartition = new TOlapTablePartition();
+        tPartition.setId(partitionId);
+        OlapTableSink.setPartitionKeys(tPartition, partitionItem, partColNum);
+        List<Tablet> partitionTabletSnapshot = new ArrayList<>();
+        for (MaterializedIndex index : 
partition.getMaterializedIndices(MaterializedIndex.IndexExtState.ALL)) {
+            List<Tablet> indexTablets = new ArrayList<>(index.getTablets());
+            tPartition.addToIndexes(new TOlapTableIndexTablets(index.getId(), 
Lists.newArrayList(
+                    
indexTablets.stream().map(Tablet::getId).collect(Collectors.toList()))));
+            tPartition.setNumBuckets(indexTablets.size());
+            partitionTabletSnapshot.addAll(indexTablets);
+        }
+        tPartition.setIsMutable(partitionInfo.getIsMutable(partitionId));
+        boolean randomDistribution =
+                partition.getDistributionInfo().getType() == 
DistributionInfo.DistributionInfoType.RANDOM;
+        boolean cacheLoadTabletIdx = (loadToSingleTablet || 
enableAdaptiveRandomBucket) && randomDistribution;
+        int quorum = 
partitionInfo.getReplicaAllocation(partitionId).getTotalReplicaNum() / 2 + 1;
+        return new PartitionResultSnapshot(partition, partitionId, tPartition, 
partitionTabletSnapshot, quorum,
+                cacheLoadTabletIdx);
+    }
+
     public TGetMetaResult getMeta(TGetMetaRequest request) throws TException {
         String clientAddr = getClientAddrAsString();
         if (LOG.isDebugEnabled()) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
index c017cd71bc2..87cb3f8e2c3 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/service/FrontendServiceImplTest.java
@@ -88,6 +88,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 public class FrontendServiceImplTest {
@@ -372,6 +373,54 @@ public class FrontendServiceImplTest {
         Assert.assertNotNull(p20230807);
     }
 
+    @Test
+    public void 
testCreatePartitionReturnsRetryErrorWhenResultPartitionIsMissing() throws 
Exception {
+        String createOlapTblStmt = "CREATE TABLE 
test.partition_dropped_before_result_snapshot(\n"
+                + "    event_day DATETIME NOT NULL,\n"
+                + "    site_id INT\n"
+                + ")\n"
+                + "DUPLICATE KEY(event_day, site_id)\n"
+                + "AUTO PARTITION BY RANGE (date_trunc(event_day, 'day')) ()\n"
+                + "DISTRIBUTED BY HASH(event_day) BUCKETS 1\n"
+                + "PROPERTIES(\"replication_num\" = \"1\");";
+        createTable(createOlapTblStmt);
+
+        Database db = 
Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");
+        OlapTable table = (OlapTable) 
db.getTableOrAnalysisException("partition_dropped_before_result_snapshot");
+        OlapTable spyTable = Mockito.spy(table);
+        String partitionName = "p20230808000000";
+        AtomicBoolean hideResultPartition = new AtomicBoolean(true);
+        Mockito.doAnswer(invocation -> {
+            Partition partition = (Partition) invocation.callRealMethod();
+            // Model the lookup result after a concurrent retention drop 
without timing-dependent test threads.
+            if (partition != null && hideResultPartition.compareAndSet(true, 
false)) {
+                return null;
+            }
+            return partition;
+        }).when(spyTable).getPartition(partitionName);
+
+        db.unregisterTable(table.getName());
+        db.registerTable(spyTable);
+        try {
+            TNullableStringLiteral start = new TNullableStringLiteral();
+            start.setValue("2023-08-08 00:00:00");
+            TCreatePartitionRequest request = new TCreatePartitionRequest();
+            request.setDbId(db.getId());
+            request.setTableId(spyTable.getId());
+            
request.setPartitionValues(Collections.singletonList(Collections.singletonList(start)));
+
+            TCreatePartitionResult result = new 
FrontendServiceImpl(exeEnv).createPartition(request);
+
+            Assert.assertEquals(TStatusCode.RUNTIME_ERROR, 
result.getStatus().getStatusCode());
+            Assert.assertTrue(result.getStatus().getErrorMsgs().get(0)
+                    .contains("was dropped concurrently while building auto 
partition result, please retry"));
+            Assert.assertFalse(hideResultPartition.get());
+        } finally {
+            db.unregisterTable(spyTable.getName());
+            db.registerTable(table);
+        }
+    }
+
     @Test
     public void testCreatePartitionList() throws Exception {
         String createOlapTblStmt = new String("CREATE TABLE 
test.partition_list(\n"


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to