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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5b9e8868c47 [ConfigNode] Skip non-existent and duplicate region IDs 
during reconstruction (#18664)
5b9e8868c47 is described below

commit 5b9e8868c475ad2699a3252862ece5dde3607d92
Author: Yongzao <[email protected]>
AuthorDate: Thu Sep 17 15:25:43 2026 +0800

    [ConfigNode] Skip non-existent and duplicate region IDs during 
reconstruction (#18664)
    
    * Avoid duplicate procedures for repeated RECONSTRUCT REGION IDs
    
    * Skip unusable reconstruct region IDs
---
 .../iotdb/confignode/i18n/ManagerMessages.java     |   6 +
 .../iotdb/confignode/i18n/ManagerMessages.java     |   6 +
 .../iotdb/confignode/manager/ProcedureManager.java |  31 ++--
 .../manager/partition/PartitionManager.java        |  21 ++-
 .../ProcedureManagerReconstructRegionTest.java     | 169 +++++++++++++++++++++
 5 files changed, 218 insertions(+), 15 deletions(-)

diff --git 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index a61e8e2aa5b..03ec8003251 100644
--- 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -289,6 +289,12 @@ public final class ManagerMessages {
       "LoadStatistics service is started successfully.";
   public static final String LOADSTATISTICS_SERVICE_IS_STOPPED_SUCCESSFULLY =
       "LoadStatistics service is stopped successfully.";
+  public static final String
+      
LOG_SKIP_DUPLICATE_REGION_ID_ARG_IN_RECONSTRUCTREGION_REQUEST_TO_DATANODE_ARG_ED195F69
 =
+          "Skip duplicate Region ID {} in ReconstructRegion request to 
DataNode {}.";
+  public static final String
+      
LOG_SKIP_NON_EXISTENT_REGION_ID_ARG_IN_RECONSTRUCTREGION_REQUEST_TO_DATANODE_ARG_7F76D789
 =
+          "Skip non-existent Region ID {} in ReconstructRegion request to 
DataNode {}.";
   public static final String 
MIGRATEREGION_SUBMIT_REGIONMIGRATEPROCEDURE_SUCCESSFULLY_REGION_ORIGIN_DATANODE 
=
       "[MigrateRegion] Submit RegionMigrateProcedure successfully, Region: {}, 
Origin DataNode: {}, Dest DataNode: {}, Add Coordinator: {}, Remove 
Coordinator: {}";
   public static final String 
SUBMIT_REGIONMIGRATEPROCEDURE_FAILED_BECAUSE_REGIONGROUP_DOESN_T_EXIST =
diff --git 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index fa8fc8e3217..f145e04052e 100644
--- 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ManagerMessages.java
@@ -287,6 +287,12 @@ public final class ManagerMessages {
       "LoadStatistics 服务已成功启动。";
   public static final String LOADSTATISTICS_SERVICE_IS_STOPPED_SUCCESSFULLY =
       "LoadStatistics 服务已成功停止。";
+  public static final String
+      
LOG_SKIP_DUPLICATE_REGION_ID_ARG_IN_RECONSTRUCTREGION_REQUEST_TO_DATANODE_ARG_ED195F69
 =
+          "跳过 ReconstructRegion 请求中重复的 Region ID {},目标 DataNode 为 {}。";
+  public static final String
+      
LOG_SKIP_NON_EXISTENT_REGION_ID_ARG_IN_RECONSTRUCTREGION_REQUEST_TO_DATANODE_ARG_7F76D789
 =
+          "跳过 ReconstructRegion 请求中不存在的 Region ID {},目标 DataNode 为 {}。";
   public static final String 
MIGRATEREGION_SUBMIT_REGIONMIGRATEPROCEDURE_SUCCESSFULLY_REGION_ORIGIN_DATANODE 
=
       "[MigrateRegion] 成功提交 RegionMigrateProcedure,Region:{},原 DataNode:{},目标 
DataNode:{},新增 Coordinator:{},移除 Coordinator:{}";
   public static final String 
SUBMIT_REGIONMIGRATEPROCEDURE_FAILED_BECAUSE_REGIONGROUP_DOESN_T_EXIST =
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
index 50b003d0494..ac1a53539f6 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java
@@ -187,6 +187,7 @@ import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -1295,17 +1296,27 @@ public class ProcedureManager {
     try (AutoCloseableLock ignoredLock =
         AutoCloseableLock.acquire(env.getSubmitRegionMigrateLock())) {
       List<ReconstructRegionProcedure> procedures = new ArrayList<>();
+      Set<Integer> seenRegionIds = new HashSet<>();
       for (int x : req.getRegionIds()) {
-        TConsensusGroupId regionId =
-            configManager
-                .getPartitionManager()
-                .generateTConsensusGroupIdByRegionId(x)
-                .orElseThrow(
-                    () ->
-                        new IllegalArgumentException(
-                            ManagerMessages.REGION_ID
-                                + x
-                                + ManagerMessages.EXCEPTION_INVALID_2928F475));
+        if (!seenRegionIds.add(x)) {
+          LOGGER.info(
+              ManagerMessages
+                  
.LOG_SKIP_DUPLICATE_REGION_ID_ARG_IN_RECONSTRUCTREGION_REQUEST_TO_DATANODE_ARG_ED195F69,
+              x,
+              req.getDataNodeId());
+          continue;
+        }
+        Optional<TConsensusGroupId> regionIdOptional =
+            
configManager.getPartitionManager().findTConsensusGroupIdByRegionId(x);
+        if (!regionIdOptional.isPresent()) {
+          LOGGER.info(
+              ManagerMessages
+                  
.LOG_SKIP_NON_EXISTENT_REGION_ID_ARG_IN_RECONSTRUCTREGION_REQUEST_TO_DATANODE_ARG_7F76D789,
+              x,
+              req.getDataNodeId());
+          continue;
+        }
+        TConsensusGroupId regionId = regionIdOptional.get();
         final TDataNodeLocation coordinator =
             handler
                 .filterDataNodeWithOtherRegionReplica(
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
index 28655923cca..2ee0bcdb60b 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java
@@ -1084,11 +1084,9 @@ public class PartitionManager {
   }
 
   public Optional<TConsensusGroupId> generateTConsensusGroupIdByRegionId(final 
int regionId) {
-    if (isRegionGroupExists(new 
TConsensusGroupId(TConsensusGroupType.SchemaRegion, regionId))) {
-      return Optional.of(new 
TConsensusGroupId(TConsensusGroupType.SchemaRegion, regionId));
-    }
-    if (isRegionGroupExists(new 
TConsensusGroupId(TConsensusGroupType.DataRegion, regionId))) {
-      return Optional.of(new TConsensusGroupId(TConsensusGroupType.DataRegion, 
regionId));
+    Optional<TConsensusGroupId> result = 
findTConsensusGroupIdByRegionId(regionId);
+    if (result.isPresent()) {
+      return result;
     }
     String msg =
         String.format(
@@ -1098,6 +1096,19 @@ public class PartitionManager {
     return Optional.empty();
   }
 
+  /**
+   * Returns the RegionGroup with the specified numeric id without logging 
when it does not exist.
+   */
+  public Optional<TConsensusGroupId> findTConsensusGroupIdByRegionId(final int 
regionId) {
+    if (isRegionGroupExists(new 
TConsensusGroupId(TConsensusGroupType.SchemaRegion, regionId))) {
+      return Optional.of(new 
TConsensusGroupId(TConsensusGroupType.SchemaRegion, regionId));
+    }
+    if (isRegionGroupExists(new 
TConsensusGroupId(TConsensusGroupType.DataRegion, regionId))) {
+      return Optional.of(new TConsensusGroupId(TConsensusGroupType.DataRegion, 
regionId));
+    }
+    return Optional.empty();
+  }
+
   /**
    * GetNodePathsPartition.
    *
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ProcedureManagerReconstructRegionTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ProcedureManagerReconstructRegionTest.java
new file mode 100644
index 00000000000..a21a5da7083
--- /dev/null
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ProcedureManagerReconstructRegionTest.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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
+ *
+ *     http://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 org.apache.iotdb.confignode.manager;
+
+import org.apache.iotdb.common.rpc.thrift.Model;
+import org.apache.iotdb.common.rpc.thrift.TConsensusGroupId;
+import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
+import org.apache.iotdb.common.rpc.thrift.TDataNodeConfiguration;
+import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
+import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.cluster.NodeStatus;
+import org.apache.iotdb.confignode.manager.node.NodeManager;
+import org.apache.iotdb.confignode.manager.partition.PartitionManager;
+import org.apache.iotdb.confignode.persistence.ProcedureInfo;
+import org.apache.iotdb.confignode.procedure.Procedure;
+import org.apache.iotdb.confignode.procedure.ProcedureExecutor;
+import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
+import org.apache.iotdb.confignode.procedure.env.RegionMaintainHandler;
+import 
org.apache.iotdb.confignode.procedure.impl.region.ReconstructRegionProcedure;
+import org.apache.iotdb.confignode.rpc.thrift.TReconstructRegionReq;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ProcedureManagerReconstructRegionTest {
+
+  private final TConsensusGroupId firstRegion =
+      new TConsensusGroupId(TConsensusGroupType.DataRegion, 12);
+  private final TConsensusGroupId secondRegion =
+      new TConsensusGroupId(TConsensusGroupType.DataRegion, 14);
+  private final TDataNodeLocation target = new 
TDataNodeLocation().setDataNodeId(7);
+  private final TDataNodeLocation coordinator = new 
TDataNodeLocation().setDataNodeId(8);
+
+  private ProcedureManager manager;
+  private ProcedureExecutor<ConfigNodeProcedureEnv> executor;
+  private PartitionManager partitionManager;
+  private final ConcurrentHashMap<Long, Procedure<ConfigNodeProcedureEnv>> 
procedures =
+      new ConcurrentHashMap<>();
+
+  @Before
+  public void setUp() throws Exception {
+    ConfigManager configManager = mock(ConfigManager.class);
+    NodeManager nodeManager = mock(NodeManager.class);
+    partitionManager = mock(PartitionManager.class);
+    ConfigNodeProcedureEnv env = mock(ConfigNodeProcedureEnv.class);
+    RegionMaintainHandler handler = mock(RegionMaintainHandler.class);
+    executor = mock(ProcedureExecutor.class);
+
+    when(configManager.getNodeManager()).thenReturn(nodeManager);
+    when(configManager.getPartitionManager()).thenReturn(partitionManager);
+    when(nodeManager.getRegisteredDataNode(target.getDataNodeId()))
+        .thenReturn(new TDataNodeConfiguration().setLocation(target));
+    when(nodeManager.filterDataNodeThroughStatus(NodeStatus.Running))
+        .thenReturn(Collections.singletonList(new 
TDataNodeConfiguration().setLocation(target)));
+    
when(partitionManager.findTConsensusGroupIdByRegionId(12)).thenReturn(Optional.of(firstRegion));
+    when(partitionManager.findTConsensusGroupIdByRegionId(14))
+        .thenReturn(Optional.of(secondRegion));
+    
when(partitionManager.findTConsensusGroupIdByRegionId(99)).thenReturn(Optional.empty());
+    
when(partitionManager.getRegionDatabase(any(TConsensusGroupId.class))).thenReturn("root.sg");
+
+    Map<TConsensusGroupId, TRegionReplicaSet> replicaSets = new HashMap<>();
+    replicaSets.put(
+        firstRegion, new TRegionReplicaSet(firstRegion, Arrays.asList(target, 
coordinator)));
+    replicaSets.put(
+        secondRegion, new TRegionReplicaSet(secondRegion, 
Arrays.asList(target, coordinator)));
+    when(partitionManager.getAllReplicaSetsMap(TConsensusGroupType.DataRegion))
+        .thenReturn(replicaSets);
+    when(partitionManager.getAllReplicaSets(target.getDataNodeId()))
+        .thenReturn(Arrays.asList(replicaSets.get(firstRegion), 
replicaSets.get(secondRegion)));
+
+    when(env.getSubmitRegionMigrateLock()).thenReturn(new ReentrantLock());
+    when(env.getRegionMaintainHandler()).thenReturn(handler);
+    when(handler.filterDataNodeWithOtherRegionReplica(
+            any(TConsensusGroupId.class),
+            eq(target),
+            eq(NodeStatus.Running),
+            eq(NodeStatus.Removing),
+            eq(NodeStatus.ReadOnly)))
+        .thenReturn(Optional.of(coordinator));
+    when(executor.getProcedures()).thenReturn(procedures);
+
+    manager = new ProcedureManager(configManager, mock(ProcedureInfo.class));
+    Field envField = ProcedureManager.class.getDeclaredField("env");
+    envField.setAccessible(true);
+    envField.set(manager, env);
+    Field executorField = ProcedureManager.class.getDeclaredField("executor");
+    executorField.setAccessible(true);
+    executorField.set(manager, executor);
+  }
+
+  @Test
+  public void testDuplicateAndNonExistentRegionIdsAreSkippedInInputOrder() {
+    TReconstructRegionReq request =
+        new TReconstructRegionReq(Arrays.asList(12, 99, 14, 12, 99, 14), 7, 
Model.TREE);
+
+    assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(), 
manager.reconstructRegion(request).getCode());
+
+    ArgumentCaptor<ReconstructRegionProcedure> captor =
+        ArgumentCaptor.forClass(ReconstructRegionProcedure.class);
+    verify(executor, times(2)).submitProcedure(captor.capture());
+    assertEquals(firstRegion, captor.getAllValues().get(0).getRegionId());
+    assertEquals(secondRegion, captor.getAllValues().get(1).getRegionId());
+    verify(partitionManager, times(1)).findTConsensusGroupIdByRegionId(12);
+    verify(partitionManager, times(1)).findTConsensusGroupIdByRegionId(14);
+    verify(partitionManager, times(1)).findTConsensusGroupIdByRegionId(99);
+  }
+
+  @Test
+  public void 
testRequestWithNoUsableRegionIdsSucceedsWithoutSubmittingProcedure() {
+    TReconstructRegionReq request = new 
TReconstructRegionReq(Arrays.asList(99, 99), 7, Model.TREE);
+
+    assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(), 
manager.reconstructRegion(request).getCode());
+    verify(executor, times(0)).submitProcedure(any());
+  }
+
+  @Test
+  public void testAnotherRequestCannotReconstructRegionWithActiveProcedure() {
+    TReconstructRegionReq request =
+        new TReconstructRegionReq(Collections.singletonList(12), 7, 
Model.TREE);
+    ReconstructRegionProcedure activeProcedure =
+        new ReconstructRegionProcedure(firstRegion, target, coordinator);
+    procedures.put(1L, activeProcedure);
+
+    TSStatus status = manager.reconstructRegion(request);
+    assertEquals(TSStatusCode.RECONSTRUCT_REGION_ERROR.getStatusCode(), 
status.getCode());
+    assertTrue(status.getMessage().contains("in progress"));
+    verify(executor, times(0)).submitProcedure(any());
+  }
+}

Reply via email to