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 a0543f5aa21 [ConfigNode] Revert procedure-based RegionGroup cleanup 
and fix RegionMaintainer retries (#18404)
a0543f5aa21 is described below

commit a0543f5aa2157b3332534d7716162c7c9d7a8fa5
Author: Yongzao <[email protected]>
AuthorDate: Wed Aug 5 19:51:36 2026 +0800

    [ConfigNode] Revert procedure-based RegionGroup cleanup and fix 
RegionMaintainer retries (#18404)
---
 .../java/org/apache/iotdb/rpc/TSStatusCode.java    |   2 +
 .../iotdb/confignode/i18n/ConfigNodeMessages.java  |   4 -
 .../iotdb/confignode/i18n/ManagerMessages.java     |   5 +-
 .../iotdb/confignode/i18n/ProcedureMessages.java   |  14 -
 .../iotdb/confignode/i18n/ConfigNodeMessages.java  |   6 -
 .../iotdb/confignode/i18n/ManagerMessages.java     |   5 +-
 .../iotdb/confignode/i18n/ProcedureMessages.java   |  16 -
 .../client/async/CnToDnAsyncRequestType.java       |   1 +
 .../CnToDnInternalServiceAsyncRequestManager.java  |   5 +
 .../iotdb/confignode/manager/ProcedureManager.java |   4 +
 .../manager/partition/PartitionManager.java        | 334 +++++++++++-------
 .../persistence/partition/PartitionInfo.java       |  29 +-
 .../impl/region/CreateRegionGroupsProcedure.java   |  44 +--
 .../impl/region/RemoveRegionGroupProcedure.java    | 389 ---------------------
 .../impl/schema/DeleteDatabaseProcedure.java       | 111 ++++--
 .../procedure/state/RemoveRegionGroupState.java    |  24 --
 .../state/schema/DeleteDatabaseState.java          |   5 +-
 .../procedure/store/ProcedureFactory.java          |   6 -
 .../confignode/procedure/store/ProcedureType.java  |   1 -
 .../PartitionManagerRegionMaintainTest.java        |  70 ++++
 .../confignode/persistence/PartitionInfoTest.java  |  27 --
 .../region/RemoveRegionGroupProcedureTest.java     | 100 ------
 .../impl/DataNodeInternalRPCServiceImpl.java       |  19 +-
 .../thrift/impl/DataNodeRegionManager.java         |  18 +-
 .../apache/iotdb/db/schemaengine/SchemaEngine.java |   5 +-
 .../iotdb/db/service/RegionMigrateService.java     |  12 +-
 .../iotdb/db/storageengine/StorageEngine.java      |   7 +-
 .../DataNodeInternalRPCServiceImplTest.java        |  26 ++
 28 files changed, 456 insertions(+), 833 deletions(-)

diff --git 
a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java 
b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
index 64c289bdf79..ae988b8d1c7 100644
--- 
a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
+++ 
b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
@@ -197,6 +197,8 @@ public enum TSStatusCode {
   RECONSTRUCT_REGION_ERROR(908),
   EXTEND_REGION_ERROR(909),
   REMOVE_REGION_PEER_ERROR(910),
+  REGION_ALREADY_EXISTS(911),
+  REGION_NOT_EXIST(912),
 
   // Cluster Manager
   ADD_CONFIGNODE_ERROR(1000),
diff --git 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
index 54e06e9a972..5e79dffbffc 100644
--- 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
@@ -642,10 +642,6 @@ public final class ConfigNodeMessages {
       "Unexpected interruption while waiting for ConfigNode leader load 
warm-up.";
   public static final String 
EXCEPTION_PROCEDURE_FILE_ARG_EXCEEDS_THE_LOAD_BUFFER_LIMIT_ARG_ACTUAL_SIZE_ARG_62375B4C
 =
       "Procedure file %s exceeds the load buffer limit %s, actual size %s";
-  public static final String 
MESSAGE_DROPPING_LEGACY_REGION_DELETE_TASK_FOR_ARG_WHILE_REPLAYING_OFFER_PLAN_REGION_DELETION_IS_NOW_HANDLED_BY_REMOVEREGIONGROUPPROCEDURE_2A81A649
 =
-      "Dropping legacy region-delete task for {} while replaying offer plan; 
region deletion is now handled by RemoveRegionGroupProcedure.";
-  public static final String 
MESSAGE_DROPPING_LEGACY_REGION_DELETE_TASK_FOR_ARG_WHILE_LOADING_SNAPSHOT_REGION_DELETION_IS_NOW_HANDLED_BY_REMOVEREGIONGROUPPROCEDURE_A9D409A0
 =
-      "Dropping legacy region-delete task for {} while loading snapshot; 
region deletion is now handled by RemoveRegionGroupProcedure.";
   public static final String 
MESSAGE_CONFIGNODE_LEADER_IS_WARMING_UP_BEFORE_SERVING_THE_REGISTERING_CONFIGNODE_WILL_WAIT_2E051639
 =
       "ConfigNode leader is warming up before serving the registering 
ConfigNode, will wait";
   public static final String 
MESSAGE_CONFIGNODE_LEADER_IS_WARMING_UP_BEFORE_SERVING_THE_REGISTERING_CONFIGNODE_WILL_WAIT_AND_RETRY_STATUS_ARG_RETRY_ARG_3C924873
 =
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 13bfaf6de8a..10ea4763af9 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
@@ -73,6 +73,7 @@ public final class ManagerMessages {
       "DataRegionGroupExtensionPolicy %s doesn't exist.";
   public static final String DECREASE_REFERENCE_COUNT_FOR_SNAPSHOT_ERROR =
       "Decrease reference count for snapshot {} error.";
+  public static final String DELETING_REGIONS_COSTS_MS = "Deleting regions 
costs {}ms";
   public static final String 
DETECTED_HISTORICAL_PIPE_COMPLETION_REPORT_FROM_DATANODE =
       "Detected historical pipe completion report from DataNode {} for pipe 
{}. remainingEventCount: {}, remainingTime: {}, completedDataNodes: {}";
   public static final String DETECTED_COMPLETION_OF_PIPE_STATIC_META_REMOVE_IT 
=
@@ -440,6 +441,8 @@ public final class ManagerMessages {
       "Start to create Region: {} on DataNode: {}";
   public static final String START_TO_CREATE_UDF_ON_DATA_NODES_NEEDTOSAVEJAR =
       "Start to create UDF [{}] on Data Nodes, needToSaveJar[{}]";
+  public static final String START_TO_DELETE_REGION_ON_DATANODE =
+      "Start to delete Region: {} on DataNode: {}";
   public static final String START_TRANSFER_OF = "Start transfer of {}";
   public static final String STOP_SUBMITTING_CQ_BECAUSE = "Stop submitting CQ 
{} because {}";
   public static final String 
STOP_SUBMITTING_CQ_BECAUSE_CURRENT_NODE_IS_NOT_LEADER_OR =
@@ -522,8 +525,6 @@ public final class ManagerMessages {
       "Unexpected interruption during waiting for configNode leader ready.";
   public static final String 
UNEXPECTED_INTERRUPTION_DURING_WAITING_FOR_GET_CLUSTER_ID =
       "Unexpected interruption during waiting for get cluster id.";
-  public static final String 
UNEXPECTED_NON_CREATE_REGION_MAINTAIN_TASK_SKIPPED =
-      "Unexpected non-create task in the RegionMaintainer queue; skipping it 
(the queue only recreates region replicas now, and region deletion is handled 
by RemoveRegionGroupProcedure).";
   public static final String 
UNEXPECTED_NULL_PROCEDURE_PARAMETERS_FOR_WAITINGPROCEDUREFINISHED =
       "Unexpected null procedure parameters for waitingProcedureFinished";
   public static final String 
UNKNOWN_DATAPARTITION_ALLOCATION_STRATEGY_USING_INHERIT_STRATEGY_BY_DEFAULT =
diff --git 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
index 1234336a79a..b29244d4117 100644
--- 
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
@@ -632,16 +632,6 @@ public final class ProcedureMessages {
   public static final String PID_ADDREGION_STATE_FAILED = "[pid{}][AddRegion] 
state {} failed";
   public static final String 
PID_ADDREGION_SUCCESS_HAS_BEEN_ADDED_TO_DATANODE_PROCEDURE_TOOK =
       "[pid{}][AddRegion] success, {} has been added to DataNode {}. Procedure 
took {} (start at {}).";
-  public static final String PID_REMOVEREGIONGROUP_STARTED_WILL_BE_DELETED =
-      "[pid{}][RemoveRegionGroup] started, region group {} will be deleted 
from DataNodes {}.";
-  public static final String 
PID_REMOVEREGIONGROUP_STARTED_REPLICA_WILL_BE_DELETED_FROM_DATANODE =
-      "[pid{}][RemoveRegionGroup] region {} will be deleted from DataNode {}.";
-  public static final String PID_REMOVEREGIONGROUP_STATE_FAILED =
-      "[pid{}][RemoveRegionGroup] state {} failed";
-  public static final String PID_REMOVEREGIONGROUP_DELETE_REPLICA_FAILED =
-      "[pid{}][RemoveRegionGroup] failed to delete a replica of region {} 
(attempt {}), will keep retrying until it is deleted. reason: {}";
-  public static final String PID_REMOVEREGIONGROUP_SUCCESS_PROCEDURE_TOOK =
-      "[pid{}][RemoveRegionGroup] success, region group {} has been deleted. 
Procedure took {} (started at {}).";
   public static final String 
PID_MIGRATEREGION_STARTED_WILL_BE_MIGRATED_FROM_DATANODE_TO =
       "[pid{}][MigrateRegion] started, {} will be migrated from DataNode {} to 
{}.";
   public static final String PID_MIGRATEREGION_STATE_COMPLETE =
@@ -1398,10 +1388,6 @@ public final class ProcedureMessages {
   public static final String LOG_ARG_8393DD4A = "{}";
   public static final String MESSAGE_HALT_PID_ARG_ACTIVECOUNT_ARG_411F3EBF = 
"Halt pid={}, activeCount={}";
   public static final String 
MESSAGE_EXCEPTION_HAPPENED_WHEN_WORKER_ARG_EXECUTE_PROCEDURE_ARG_6E3AD27D = 
"Exception happened when worker {} execute procedure {}";
-  public static final String 
EXCEPTION_CANNOT_DERIVE_A_COLLISION_FREE_DELETE_TASKID_PROCID_ARG_DELETETASKSEQ_ARG_EXCEED_THE_71B7046A
 =
-      "cannot derive a collision-free delete taskId: procId=%d, 
deleteTaskSeq=%d exceed the ";
-  public static final String 
EXCEPTION_CANNOT_DERIVE_A_COLLISION_FREE_DELETE_TASKID_PROCID_ARG_DELETETASKSEQ_ARG_EXCEED_THE_ARG_ARG_BIT_BUDGET_015C598D
 =
-      "cannot derive a collision-free delete taskId: procId=%d, 
deleteTaskSeq=%d exceed the %d/%d-bit budget";
   public static final String 
MESSAGE_FAILED_TO_SHOW_DATAPARTITIONTABLE_INTEGRITY_CHECK_PROGRESS_5EE98694 = 
"Failed to show DataPartitionTable integrity check progress";
   public static final String 
MESSAGE_ENCOUNTERED_UNEXPECTED_DATAPARTITIONTABLEINTEGRITYCHECKPROCEDURESTATE_ARG_WHEN_SHOWING_PROGRESS_5FA2739F
 =
       "Encountered unexpected DataPartitionTableIntegrityCheckProcedureState 
{} when showing progress";
diff --git 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
index 0c28efc4096..5f9a0d8d9ab 100644
--- 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ConfigNodeMessages.java
@@ -679,12 +679,6 @@ public final class ConfigNodeMessages {
   public static final String
       
EXCEPTION_PROCEDURE_FILE_ARG_EXCEEDS_THE_LOAD_BUFFER_LIMIT_ARG_ACTUAL_SIZE_ARG_62375B4C
 =
           "Procedure 文件 %s 超过了加载缓冲区限制 %s,实际大小为 %s";
-  public static final String
-      
MESSAGE_DROPPING_LEGACY_REGION_DELETE_TASK_FOR_ARG_WHILE_REPLAYING_OFFER_PLAN_REGION_DELETION_IS_NOW_HANDLED_BY_REMOVEREGIONGROUPPROCEDURE_2A81A649
 =
-          "重放 offer plan 时丢弃 {} 的遗留 region-delete 任务;region 删除现已由 
RemoveRegionGroupProcedure 处理。";
-  public static final String
-      
MESSAGE_DROPPING_LEGACY_REGION_DELETE_TASK_FOR_ARG_WHILE_LOADING_SNAPSHOT_REGION_DELETION_IS_NOW_HANDLED_BY_REMOVEREGIONGROUPPROCEDURE_A9D409A0
 =
-          "加载快照时丢弃 {} 的遗留 region-delete 任务;region 删除现已由 
RemoveRegionGroupProcedure 处理。";
   public static final String
       
MESSAGE_CONFIGNODE_LEADER_IS_WARMING_UP_BEFORE_SERVING_THE_REGISTERING_CONFIGNODE_WILL_WAIT_2E051639
 =
           "ConfigNode leader 在服务注册中的 ConfigNode 前正在进行预热,将等待";
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 42aa21f46ae..499e922f7a7 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
@@ -73,6 +73,7 @@ public final class ManagerMessages {
       "DataRegionGroupExtensionPolicy %s 不存在。";
   public static final String DECREASE_REFERENCE_COUNT_FOR_SNAPSHOT_ERROR =
       "减少快照 {} 的引用计数失败。";
+  public static final String DELETING_REGIONS_COSTS_MS = "删除 region 耗时 {}ms";
   public static final String 
DETECTED_HISTORICAL_PIPE_COMPLETION_REPORT_FROM_DATANODE =
       "检测到来自 DataNode {} 的历史 pipe 完成上报,pipe {}。remainingEventCount: {}, 
remainingTime: {}, completedDataNodes: {}";
   public static final String DETECTED_COMPLETION_OF_PIPE_STATIC_META_REMOVE_IT 
=
@@ -435,6 +436,8 @@ public final class ManagerMessages {
       "开始在 DataNode: {} 上创建 Region:{}";
   public static final String START_TO_CREATE_UDF_ON_DATA_NODES_NEEDTOSAVEJAR =
       "开始在 Data Nodes 上创建 UDF [{}],needToSaveJar[{}]";
+  public static final String START_TO_DELETE_REGION_ON_DATANODE =
+      "开始在 DataNode: {} 上删除 Region:{}";
   public static final String START_TRANSFER_OF = "开始传输 {}";
   public static final String STOP_SUBMITTING_CQ_BECAUSE = "停止提交 CQ {},原因:{}";
   public static final String 
STOP_SUBMITTING_CQ_BECAUSE_CURRENT_NODE_IS_NOT_LEADER_OR =
@@ -517,8 +520,6 @@ public final class ManagerMessages {
       "等待 configNode leader 就绪过程中发生意外中断。";
   public static final String 
UNEXPECTED_INTERRUPTION_DURING_WAITING_FOR_GET_CLUSTER_ID =
       "等待获取 cluster id 过程中发生意外中断。";
-  public static final String 
UNEXPECTED_NON_CREATE_REGION_MAINTAIN_TASK_SKIPPED =
-      "RegionMaintainer 队列中出现意外的非 create 任务;跳过处理(该队列目前仅用于重建 region 副本,region 
删除由 RemoveRegionGroupProcedure 处理)。";
   public static final String 
UNEXPECTED_NULL_PROCEDURE_PARAMETERS_FOR_WAITINGPROCEDUREFINISHED =
       "waitingProcedureFinished 的 procedure 参数为空";
   public static final String 
UNKNOWN_DATAPARTITION_ALLOCATION_STRATEGY_USING_INHERIT_STRATEGY_BY_DEFAULT =
diff --git 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
index 624a82278e7..0569e1b6261 100644
--- 
a/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
+++ 
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
@@ -598,16 +598,6 @@ public final class ProcedureMessages {
   public static final String PID_ADDREGION_STATE_FAILED = "[pid{}][AddRegion] 
状态 {} 失败";
   public static final String 
PID_ADDREGION_SUCCESS_HAS_BEEN_ADDED_TO_DATANODE_PROCEDURE_TOOK =
       "[pid{}][AddRegion] 成功,{} 已添加到 DataNode {}。Procedure 耗时 {}(开始于 {})。";
-  public static final String PID_REMOVEREGIONGROUP_STARTED_WILL_BE_DELETED =
-      "[pid{}][RemoveRegionGroup] 开始,region group {} 将从 DataNode {} 上删除。";
-  public static final String 
PID_REMOVEREGIONGROUP_STARTED_REPLICA_WILL_BE_DELETED_FROM_DATANODE =
-      "[pid{}][RemoveRegionGroup] region {} 将从 DataNode {} 上删除。";
-  public static final String PID_REMOVEREGIONGROUP_STATE_FAILED =
-      "[pid{}][RemoveRegionGroup] 状态 {} 失败";
-  public static final String PID_REMOVEREGIONGROUP_DELETE_REPLICA_FAILED =
-      "[pid{}][RemoveRegionGroup] 删除 region {} 的一个副本失败(第 {} 
次尝试),将持续重试直到删除成功。原因:{}";
-  public static final String PID_REMOVEREGIONGROUP_SUCCESS_PROCEDURE_TOOK =
-      "[pid{}][RemoveRegionGroup] 成功,region group {} 已删除。过程耗时 {}(开始于 {})。";
   public static final String 
PID_MIGRATEREGION_STARTED_WILL_BE_MIGRATED_FROM_DATANODE_TO =
       "[pid{}][MigrateRegion] 开始,{} 将从 DataNode {} 迁移到 {}。";
   public static final String PID_MIGRATEREGION_STATE_COMPLETE = 
"[pid{}][MigrateRegion] 状态 {} 完成";
@@ -1469,12 +1459,6 @@ public final class ProcedureMessages {
   public static final String
       
MESSAGE_EXCEPTION_HAPPENED_WHEN_WORKER_ARG_EXECUTE_PROCEDURE_ARG_6E3AD27D =
           "worker {} 执行 procedure {} 时发生异常";
-  public static final String
-      
EXCEPTION_CANNOT_DERIVE_A_COLLISION_FREE_DELETE_TASKID_PROCID_ARG_DELETETASKSEQ_ARG_EXCEED_THE_71B7046A
 =
-          "无法推导出无冲突的 delete taskId:procId=%d,deleteTaskSeq=%d 超出了 ";
-  public static final String
-      
EXCEPTION_CANNOT_DERIVE_A_COLLISION_FREE_DELETE_TASKID_PROCID_ARG_DELETETASKSEQ_ARG_EXCEED_THE_ARG_ARG_BIT_BUDGET_015C598D
 =
-          "无法推导出无冲突的 delete taskId:procId=%d,deleteTaskSeq=%d 超出了 %d/%d 位的预算";
   public static final String
       
MESSAGE_FAILED_TO_SHOW_DATAPARTITIONTABLE_INTEGRITY_CHECK_PROGRESS_5EE98694 =
           "显示 DataPartitionTable 完整性检查进度失败";
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
index cc28fed0ff5..2946884e373 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java
@@ -39,6 +39,7 @@ public enum CnToDnAsyncRequestType {
   // Region Maintenance
   CREATE_DATA_REGION,
   CREATE_SCHEMA_REGION,
+  DELETE_REGION,
   RESET_PEER_LIST,
   NOTIFY_REGION_MIGRATION,
   UPDATE_REGION_ROUTE_MAP,
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
index 9c90c469137..4f66330f6ec 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java
@@ -19,6 +19,7 @@
 
 package org.apache.iotdb.confignode.client.async;
 
+import org.apache.iotdb.common.rpc.thrift.TConsensusGroupId;
 import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
 import org.apache.iotdb.common.rpc.thrift.TFlushReq;
 import org.apache.iotdb.common.rpc.thrift.TNodeLocations;
@@ -142,6 +143,10 @@ public class CnToDnInternalServiceAsyncRequestManager
         (req, client, handler) ->
             client.createDataRegion(
                 (TCreateDataRegionReq) req, (DataNodeTSStatusRPCHandler) 
handler));
+    actionMapBuilder.put(
+        CnToDnAsyncRequestType.DELETE_REGION,
+        (req, client, handler) ->
+            client.deleteRegion((TConsensusGroupId) req, 
(DataNodeTSStatusRPCHandler) handler));
     actionMapBuilder.put(
         CnToDnAsyncRequestType.CREATE_SCHEMA_REGION,
         (req, client, handler) ->
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 bd987cd42a6..45cea0a31e3 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
@@ -59,6 +59,7 @@ import 
org.apache.iotdb.confignode.consensus.request.write.datanode.RemoveDataNo
 import 
org.apache.iotdb.confignode.consensus.request.write.procedure.UpdateProcedurePlan;
 import 
org.apache.iotdb.confignode.consensus.request.write.region.CreateRegionGroupsPlan;
 import org.apache.iotdb.confignode.i18n.ManagerMessages;
+import org.apache.iotdb.confignode.manager.partition.PartitionManager;
 import 
org.apache.iotdb.confignode.manager.subscription.SubscriptionCoordinator;
 import org.apache.iotdb.confignode.persistence.ProcedureInfo;
 import org.apache.iotdb.confignode.procedure.PartitionTableAutoCleaner;
@@ -339,6 +340,9 @@ public class ProcedureManager {
     }
     List<TSStatus> results = new ArrayList<>(procedures.size());
     procedures.forEach(procedure -> 
results.add(waitingProcedureFinished(procedure)));
+    // Clear the previously deleted regions
+    final PartitionManager partitionManager = 
getConfigManager().getPartitionManager();
+    
partitionManager.getRegionMaintainer().submit(partitionManager::maintainRegionReplicas);
     if (results.stream()
         .allMatch(result -> result.getCode() == 
TSStatusCode.SUCCESS_STATUS.getStatusCode())) {
       return StatusUtils.OK;
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 7efdae278db..28655923cca 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
@@ -84,7 +84,9 @@ import org.apache.iotdb.confignode.manager.node.NodeManager;
 import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager;
 import org.apache.iotdb.confignode.persistence.partition.PartitionInfo;
 import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
+import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionDeleteTask;
 import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainTask;
+import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainType;
 import 
org.apache.iotdb.confignode.procedure.impl.partition.DataPartitionTableIntegrityCheckProcedure;
 import org.apache.iotdb.confignode.rpc.thrift.TCountTimeSlotListReq;
 import org.apache.iotdb.confignode.rpc.thrift.TGetRegionGroupsByTimeReq;
@@ -108,7 +110,6 @@ import org.slf4j.LoggerFactory;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -1356,139 +1357,228 @@ public class PartitionManager {
   /**
    * Called by {@link PartitionManager#regionMaintainer}.
    *
-   * <p>Periodically recreate the failed RegionReplicas offered to the 
RegionMaintainer queue.
-   * Region deletion is owned by {@code RemoveRegionGroupProcedure} and is no 
longer handled here.
+   * <p>Periodically maintain the RegionReplicas to be created or deleted
    */
   public void maintainRegionReplicas() {
     // The consensusManager of configManager may not be fully initialized at 
this time
-    if (getConsensusManager() == null || !getConsensusManager().isLeader()) {
-      return;
-    }
-
-    // Group the queued tasks into one FIFO sub-queue per region. The queue 
only ever holds
-    // RegionCreateTasks now (delete tasks are filtered out at the 
PartitionInfo ingestion points),
-    // and a region may carry several of them when more than one of its 
replicas failed to create.
-    final Map<TConsensusGroupId, Queue<RegionCreateTask>> tasksByRegion = new 
HashMap<>();
-    for (RegionMaintainTask task : partitionInfo.getRegionMaintainEntryList()) 
{
-      if (!(task instanceof RegionCreateTask)) {
-        // Unreachable: the queue only holds create tasks now (legacy delete 
tasks are dropped at
-        // the
-        // PartitionInfo ingestion points). Guard against a regression so an 
unexpected task type
-        // cannot silently stall the loop.
-        
LOGGER.warn(ManagerMessages.UNEXPECTED_NON_CREATE_REGION_MAINTAIN_TASK_SKIPPED);
-        continue;
-      }
-      tasksByRegion
-          .computeIfAbsent(task.getRegionId(), k -> new LinkedList<>())
-          .add((RegionCreateTask) task);
-    }
-
-    // Drain the sub-queues head-by-head. Each round takes the head of every 
region, batches those
-    // heads by region type into a single create RPC per type, then durably 
polls the tasks that
-    // succeeded. Tasks of the same region are advanced one at a time to 
preserve their offer order.
-    while (!tasksByRegion.isEmpty()) {
-      final Map<TConsensusGroupType, List<RegionCreateTask>> headsByType =
-          new EnumMap<>(TConsensusGroupType.class);
-      for (Queue<RegionCreateTask> queue : tasksByRegion.values()) {
-        final RegionCreateTask head = queue.peek();
-        headsByType.computeIfAbsent(head.getRegionId().getType(), k -> new 
ArrayList<>()).add(head);
-      }
+    Optional.ofNullable(getConsensusManager())
+        .ifPresent(
+            consensusManager -> {
+              if (getConsensusManager().isLeader()) {
+                List<RegionMaintainTask> regionMaintainTaskList =
+                    partitionInfo.getRegionMaintainEntryList();
+
+                if (regionMaintainTaskList.isEmpty()) {
+                  return;
+                }
+
+                // Group tasks by region id
+                Map<TConsensusGroupId, Queue<RegionMaintainTask>> 
regionMaintainTaskMap =
+                    new HashMap<>();
+                for (RegionMaintainTask regionMaintainTask : 
regionMaintainTaskList) {
+                  regionMaintainTaskMap
+                      .computeIfAbsent(regionMaintainTask.getRegionId(), k -> 
new LinkedList<>())
+                      .add(regionMaintainTask);
+                }
+
+                while (!regionMaintainTaskMap.isEmpty()) {
+                  // Select same type task from each region group
+                  List<RegionMaintainTask> selectedRegionMaintainTask = new 
ArrayList<>();
+                  RegionMaintainType currentType = null;
+                  for (Map.Entry<TConsensusGroupId, Queue<RegionMaintainTask>> 
entry :
+                      regionMaintainTaskMap.entrySet()) {
+                    RegionMaintainTask regionMaintainTask = 
entry.getValue().peek();
+                    if (regionMaintainTask == null) {
+                      continue;
+                    }
+
+                    if (currentType == null) {
+                      currentType = regionMaintainTask.getType();
+                      selectedRegionMaintainTask.add(entry.getValue().peek());
+                    } else {
+                      if (!currentType.equals(regionMaintainTask.getType())) {
+                        continue;
+                      }
+
+                      if (currentType.equals(RegionMaintainType.DELETE)
+                          || entry
+                              .getKey()
+                              .getType()
+                              
.equals(selectedRegionMaintainTask.get(0).getRegionId().getType())) {
+                        // Delete or same create task
+                        
selectedRegionMaintainTask.add(entry.getValue().peek());
+                      }
+                    }
+                  }
 
-      final Set<TConsensusGroupId> successfulRegions = new HashSet<>();
-      int selectedCount = 0;
-      for (Map.Entry<TConsensusGroupType, List<RegionCreateTask>> entry : 
headsByType.entrySet()) {
-        selectedCount += entry.getValue().size();
-        successfulRegions.addAll(submitRegionCreateTasks(entry.getKey(), 
entry.getValue()));
-      }
+                  if (selectedRegionMaintainTask.isEmpty()) {
+                    break;
+                  }
 
-      if (successfulRegions.isEmpty()) {
-        break;
-      }
+                  Set<TConsensusGroupId> successfulTask = new HashSet<>();
+                  switch (currentType) {
+                    case CREATE:
+                      // create region
+                      switch 
(selectedRegionMaintainTask.get(0).getRegionId().getType()) {
+                        case SchemaRegion:
+                          // create SchemaRegion
+                          DataNodeAsyncRequestContext<TCreateSchemaRegionReq, 
TSStatus>
+                              createSchemaRegionHandler =
+                                  new DataNodeAsyncRequestContext<>(
+                                      
CnToDnAsyncRequestType.CREATE_SCHEMA_REGION);
+                          for (RegionMaintainTask regionMaintainTask : 
selectedRegionMaintainTask) {
+                            RegionCreateTask schemaRegionCreateTask =
+                                (RegionCreateTask) regionMaintainTask;
+                            LOGGER.info(
+                                
ManagerMessages.START_TO_CREATE_REGION_ON_DATANODE,
+                                
schemaRegionCreateTask.getRegionReplicaSet().getRegionId(),
+                                schemaRegionCreateTask.getTargetDataNode());
+                            createSchemaRegionHandler.putRequest(
+                                schemaRegionCreateTask.getRegionId().getId(),
+                                new TCreateSchemaRegionReq(
+                                    
schemaRegionCreateTask.getRegionReplicaSet(),
+                                    schemaRegionCreateTask.getStorageGroup()));
+                            createSchemaRegionHandler.putNodeLocation(
+                                schemaRegionCreateTask.getRegionId().getId(),
+                                schemaRegionCreateTask.getTargetDataNode());
+                          }
+
+                          
CnToDnInternalServiceAsyncRequestManager.getInstance()
+                              
.sendAsyncRequestWithRetry(createSchemaRegionHandler);
+
+                          for (Map.Entry<Integer, TSStatus> entry :
+                              
createSchemaRegionHandler.getResponseMap().entrySet()) {
+                            if (isRegionMaintainTaskCompleted(
+                                RegionMaintainType.CREATE, entry.getValue())) {
+                              successfulTask.add(
+                                  new TConsensusGroupId(
+                                      TConsensusGroupType.SchemaRegion, 
entry.getKey()));
+                            }
+                          }
+                          break;
+                        case DataRegion:
+                          // Create DataRegion
+                          DataNodeAsyncRequestContext<TCreateDataRegionReq, 
TSStatus>
+                              createDataRegionHandler =
+                                  new DataNodeAsyncRequestContext<>(
+                                      
CnToDnAsyncRequestType.CREATE_DATA_REGION);
+                          for (RegionMaintainTask regionMaintainTask : 
selectedRegionMaintainTask) {
+                            RegionCreateTask dataRegionCreateTask =
+                                (RegionCreateTask) regionMaintainTask;
+                            LOGGER.info(
+                                
ManagerMessages.START_TO_CREATE_REGION_ON_DATANODE,
+                                
dataRegionCreateTask.getRegionReplicaSet().getRegionId(),
+                                dataRegionCreateTask.getTargetDataNode());
+                            createDataRegionHandler.putRequest(
+                                dataRegionCreateTask.getRegionId().getId(),
+                                new TCreateDataRegionReq(
+                                    dataRegionCreateTask.getRegionReplicaSet(),
+                                    dataRegionCreateTask.getStorageGroup()));
+                            createDataRegionHandler.putNodeLocation(
+                                dataRegionCreateTask.getRegionId().getId(),
+                                dataRegionCreateTask.getTargetDataNode());
+                          }
+
+                          
CnToDnInternalServiceAsyncRequestManager.getInstance()
+                              
.sendAsyncRequestWithRetry(createDataRegionHandler);
+
+                          for (Map.Entry<Integer, TSStatus> entry :
+                              
createDataRegionHandler.getResponseMap().entrySet()) {
+                            if (isRegionMaintainTaskCompleted(
+                                RegionMaintainType.CREATE, entry.getValue())) {
+                              successfulTask.add(
+                                  new TConsensusGroupId(
+                                      TConsensusGroupType.DataRegion, 
entry.getKey()));
+                            }
+                          }
+                          break;
+                      }
+                      break;
+                    case DELETE:
+                      // delete region
+                      DataNodeAsyncRequestContext<TConsensusGroupId, TSStatus> 
deleteRegionHandler =
+                          new 
DataNodeAsyncRequestContext<>(CnToDnAsyncRequestType.DELETE_REGION);
+                      Map<Integer, TConsensusGroupId> regionIdMap = new 
HashMap<>();
+                      for (RegionMaintainTask regionMaintainTask : 
selectedRegionMaintainTask) {
+                        RegionDeleteTask regionDeleteTask = (RegionDeleteTask) 
regionMaintainTask;
+                        LOGGER.info(
+                            ManagerMessages.START_TO_DELETE_REGION_ON_DATANODE,
+                            regionDeleteTask.getRegionId(),
+                            regionDeleteTask.getTargetDataNode());
+                        deleteRegionHandler.putRequest(
+                            regionDeleteTask.getRegionId().getId(), 
regionDeleteTask.getRegionId());
+                        deleteRegionHandler.putNodeLocation(
+                            regionDeleteTask.getRegionId().getId(),
+                            regionDeleteTask.getTargetDataNode());
+                        regionIdMap.put(
+                            regionDeleteTask.getRegionId().getId(), 
regionDeleteTask.getRegionId());
+                      }
+
+                      long startTime = System.currentTimeMillis();
+                      CnToDnInternalServiceAsyncRequestManager.getInstance()
+                          .sendAsyncRequestWithRetry(deleteRegionHandler);
+
+                      LOGGER.info(
+                          ManagerMessages.DELETING_REGIONS_COSTS_MS,
+                          (System.currentTimeMillis() - startTime));
+
+                      for (Map.Entry<Integer, TSStatus> entry :
+                          deleteRegionHandler.getResponseMap().entrySet()) {
+                        if (isRegionMaintainTaskCompleted(
+                            RegionMaintainType.DELETE, entry.getValue())) {
+                          successfulTask.add(regionIdMap.get(entry.getKey()));
+                        }
+                      }
+                      break;
+                  }
 
-      // Advance the in-memory sub-queues so the next round picks the 
following task of each region.
-      for (TConsensusGroupId regionId : successfulRegions) {
-        tasksByRegion.computeIfPresent(
-            regionId,
-            (k, queue) -> {
-              queue.poll();
-              return queue.isEmpty() ? null : queue;
-            });
-      }
+                  if (successfulTask.isEmpty()) {
+                    break;
+                  }
 
-      // Durably remove the head of every successfully created region from the 
persisted queue.
-      try {
-        getConsensusManager().write(new 
PollSpecificRegionMaintainTaskPlan(successfulRegions));
-      } catch (ConsensusException e) {
-        LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
-      }
+                  for (TConsensusGroupId regionId : successfulTask) {
+                    regionMaintainTaskMap.compute(
+                        regionId,
+                        (k, v) -> {
+                          if (v == null) {
+                            throw new IllegalStateException();
+                          }
+                          v.poll();
+                          if (v.isEmpty()) {
+                            return null;
+                          } else {
+                            return v;
+                          }
+                        });
+                  }
 
-      if (successfulRegions.size() < selectedCount) {
-        // Some tasks failed this round; stop and retry on the next schedule 
so that the tasks of
-        // each region keep being executed in the order they were offered.
-        break;
-      }
-    }
-  }
+                  // Poll the head entry if success
+                  try {
+                    getConsensusManager()
+                        .write(new 
PollSpecificRegionMaintainTaskPlan(successfulTask));
+                  } catch (ConsensusException e) {
+                    LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
+                  }
 
-  /**
-   * Send a batched create RPC for the given heads, all of which share the 
given region type, and
-   * return the ids of the regions whose replica was created successfully.
-   */
-  private Set<TConsensusGroupId> submitRegionCreateTasks(
-      TConsensusGroupType regionType, List<RegionCreateTask> createTasks) {
-    final Set<TConsensusGroupId> successfulRegions = new HashSet<>();
-    switch (regionType) {
-      case SchemaRegion:
-        final DataNodeAsyncRequestContext<TCreateSchemaRegionReq, TSStatus> 
schemaHandler =
-            new 
DataNodeAsyncRequestContext<>(CnToDnAsyncRequestType.CREATE_SCHEMA_REGION);
-        for (RegionCreateTask task : createTasks) {
-          LOGGER.info(
-              ManagerMessages.START_TO_CREATE_REGION_ON_DATANODE,
-              task.getRegionReplicaSet().getRegionId(),
-              task.getTargetDataNode());
-          schemaHandler.putRequest(
-              task.getRegionId().getId(),
-              new TCreateSchemaRegionReq(task.getRegionReplicaSet(), 
task.getStorageGroup()));
-          schemaHandler.putNodeLocation(task.getRegionId().getId(), 
task.getTargetDataNode());
-        }
-        CnToDnInternalServiceAsyncRequestManager.getInstance()
-            .sendAsyncRequestWithRetry(schemaHandler);
-        collectSuccessfulRegions(
-            schemaHandler.getResponseMap(), TConsensusGroupType.SchemaRegion, 
successfulRegions);
-        break;
-      case DataRegion:
-        final DataNodeAsyncRequestContext<TCreateDataRegionReq, TSStatus> 
dataHandler =
-            new 
DataNodeAsyncRequestContext<>(CnToDnAsyncRequestType.CREATE_DATA_REGION);
-        for (RegionCreateTask task : createTasks) {
-          LOGGER.info(
-              ManagerMessages.START_TO_CREATE_REGION_ON_DATANODE,
-              task.getRegionReplicaSet().getRegionId(),
-              task.getTargetDataNode());
-          dataHandler.putRequest(
-              task.getRegionId().getId(),
-              new TCreateDataRegionReq(task.getRegionReplicaSet(), 
task.getStorageGroup()));
-          dataHandler.putNodeLocation(task.getRegionId().getId(), 
task.getTargetDataNode());
-        }
-        CnToDnInternalServiceAsyncRequestManager.getInstance()
-            .sendAsyncRequestWithRetry(dataHandler);
-        collectSuccessfulRegions(
-            dataHandler.getResponseMap(), TConsensusGroupType.DataRegion, 
successfulRegions);
-        break;
-      default:
-        break;
-    }
-    return successfulRegions;
+                  if (successfulTask.size() < 
selectedRegionMaintainTask.size()) {
+                    // Here we just break and wait until next schedule task
+                    // due to all the RegionMaintainEntry should be executed by
+                    // the order of they were offered
+                    break;
+                  }
+                }
+              }
+            });
   }
 
-  private void collectSuccessfulRegions(
-      Map<Integer, TSStatus> responseMap,
-      TConsensusGroupType regionType,
-      Set<TConsensusGroupId> successfulRegions) {
-    for (Map.Entry<Integer, TSStatus> entry : responseMap.entrySet()) {
-      if (entry.getValue().getCode() == 
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
-        successfulRegions.add(new TConsensusGroupId(regionType, 
entry.getKey()));
-      }
+  static boolean isRegionMaintainTaskCompleted(
+      RegionMaintainType regionMaintainType, TSStatus status) {
+    if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+      return true;
     }
+    return regionMaintainType == RegionMaintainType.CREATE
+        ? status.getCode() == 
TSStatusCode.REGION_ALREADY_EXISTS.getStatusCode()
+        : status.getCode() == TSStatusCode.REGION_NOT_EXIST.getStatusCode();
   }
 
   public void startRegionCleaner() {
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
index da2466c1e24..8b2079b581d 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/partition/PartitionInfo.java
@@ -64,7 +64,6 @@ import 
org.apache.iotdb.confignode.consensus.response.partition.SchemaPartitionR
 import org.apache.iotdb.confignode.exception.DatabaseNotExistsException;
 import org.apache.iotdb.confignode.i18n.ConfigNodeMessages;
 import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainTask;
-import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainType;
 import org.apache.iotdb.confignode.rpc.thrift.TRegionInfo;
 import org.apache.iotdb.confignode.rpc.thrift.TShowRegionReq;
 import org.apache.iotdb.confignode.rpc.thrift.TTimeSlotList;
@@ -233,20 +232,7 @@ public class PartitionInfo implements SnapshotProcessor {
   public TSStatus offerRegionMaintainTasks(
       OfferRegionMaintainTasksPlan offerRegionMaintainTasksPlan) {
     synchronized (regionMaintainTaskList) {
-      // The RegionMaintainer queue only recreates failed region replicas now; 
region deletion is
-      // owned by RemoveRegionGroupProcedure. Drop any legacy DELETE task that 
an upgraded node may
-      // replay from an old consensus log, so it cannot get stuck in the queue 
and block the
-      // recreation of that region's other replicas.
-      for (RegionMaintainTask task : 
offerRegionMaintainTasksPlan.getRegionMaintainTaskList()) {
-        if (RegionMaintainType.DELETE.equals(task.getType())) {
-          LOGGER.info(
-              ConfigNodeMessages
-                  
.MESSAGE_DROPPING_LEGACY_REGION_DELETE_TASK_FOR_ARG_WHILE_REPLAYING_OFFER_PLAN_REGION_DELETION_IS_NOW_HANDLED_BY_REMOVEREGIONGROUPPROCEDURE_2A81A649,
-              task.getRegionId());
-          continue;
-        }
-        regionMaintainTaskList.add(task);
-      }
+      
regionMaintainTaskList.addAll(offerRegionMaintainTasksPlan.getRegionMaintainTaskList());
       return RpcUtils.SUCCESS_STATUS;
     }
   }
@@ -1088,22 +1074,11 @@ public class PartitionInfo implements SnapshotProcessor 
{
         databasePartitionTables.put(database, databasePartitionTable);
       }
 
-      // restore the RegionMaintainer queue
+      // restore deletedRegionSet
       length = ReadWriteIOUtils.readInt(fileInputStream);
       for (int i = 0; i < length; i++) {
         final RegionMaintainTask task =
             RegionMaintainTask.Factory.create(fileInputStream, protocol);
-        // The RegionMaintainer queue only recreates failed region replicas 
now; region deletion is
-        // owned by RemoveRegionGroupProcedure. Drop any legacy DELETE task 
carried over from an
-        // upgraded snapshot so it cannot get stuck at the head of a region's 
queue and block the
-        // recreation of that region's other replicas.
-        if (RegionMaintainType.DELETE.equals(task.getType())) {
-          LOGGER.info(
-              ConfigNodeMessages
-                  
.MESSAGE_DROPPING_LEGACY_REGION_DELETE_TASK_FOR_ARG_WHILE_LOADING_SNAPSHOT_REGION_DELETION_IS_NOW_HANDLED_BY_REMOVEREGIONGROUPPROCEDURE_A9D409A0,
-              task.getRegionId());
-          continue;
-        }
         regionMaintainTaskList.add(task);
       }
     }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/CreateRegionGroupsProcedure.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/CreateRegionGroupsProcedure.java
index 276cdf432d9..e9cce807e77 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/CreateRegionGroupsProcedure.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/CreateRegionGroupsProcedure.java
@@ -36,6 +36,7 @@ import org.apache.iotdb.confignode.i18n.ConfigNodeMessages;
 import org.apache.iotdb.confignode.i18n.ProcedureMessages;
 import 
org.apache.iotdb.confignode.manager.load.cache.region.RegionHeartbeatSample;
 import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
+import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionDeleteTask;
 import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
 import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
 import org.apache.iotdb.confignode.procedure.impl.StateMachineProcedure;
@@ -50,9 +51,7 @@ import org.slf4j.LoggerFactory;
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -108,12 +107,6 @@ public class CreateRegionGroupsProcedure
       case SHUNT_REGION_REPLICAS:
         persistPlan = new CreateRegionGroupsPlan();
         final OfferRegionMaintainTasksPlan offerPlan = new 
OfferRegionMaintainTasksPlan();
-        // RegionGroups that failed to reach a serving quorum have their 
redundant (already-created)
-        // replicas removed via an independent root 
RemoveRegionGroupProcedure. Submitting them as
-        // root procedures (instead of children) keeps this procedure from 
waiting for or being
-        // failed by the cleanup: each one retries forever until those 
replicas are deleted, while
-        // this procedure proceeds to activate the region groups that did form 
a quorum.
-        final List<RemoveRegionGroupProcedure> removeRegionGroupProcedures = 
new ArrayList<>();
         // Filter those RegionGroups that created successfully
         createRegionGroupsPlan
             .getRegionGroupMap()
@@ -161,11 +154,7 @@ public class CreateRegionGroupsProcedure
                                       
.CREATEREGIONGROUPS_FAILED_TO_CREATE_SOME_REPLICAS_OF_REGIONGROUP_BUT_THIS,
                                   regionReplicaSet.getRegionId());
                             } else {
-                              // The redundant RegionReplicas (the ones that 
did get created) should
-                              // be deleted otherwise
-                              final TRegionReplicaSet redundantReplicas =
-                                  new TRegionReplicaSet()
-                                      
.setRegionId(regionReplicaSet.getRegionId());
+                              // The redundant RegionReplicas should be 
deleted otherwise
                               regionReplicaSet
                                   .getDataNodeLocations()
                                   .forEach(
@@ -173,13 +162,12 @@ public class CreateRegionGroupsProcedure
                                         if (!failedRegionReplicas
                                             .getDataNodeLocations()
                                             .contains(targetDataNode)) {
-                                          
redundantReplicas.addToDataNodeLocations(targetDataNode);
+                                          RegionDeleteTask deleteTask =
+                                              new RegionDeleteTask(
+                                                  targetDataNode, 
regionReplicaSet.getRegionId());
+                                          
offerPlan.appendRegionMaintainTask(deleteTask);
                                         }
                                       });
-                              if (redundantReplicas.getDataNodeLocationsSize() 
> 0) {
-                                removeRegionGroupProcedures.add(
-                                    new 
RemoveRegionGroupProcedure(redundantReplicas));
-                              }
 
                               LOGGER.info(
                                   ProcedureMessages
@@ -200,26 +188,6 @@ public class CreateRegionGroupsProcedure
           LOGGER.warn(
               
ConfigNodeMessages.FAILED_IN_THE_WRITE_API_EXECUTING_THE_CONSENSUS_LAYER_DUE, 
e);
         }
-        // Submit the redundant-replica cleanups as independent root 
procedures. This is
-        // intentionally NOT guarded by isStateDeserialized(): the executor 
persists a procedure at
-        // a state BEFORE that state's body has run (it advances the state on 
the previous cycle,
-        // then may stop at the inter-state boundary on a leader switch — see
-        // ProcedureExecutor#executeProcedure), so a recovery that lands on 
SHUNT_REGION_REPLICAS
-        // means the submissions have NOT happened yet. Skipping them would 
leave the
-        // already-created
-        // replicas of sub-quorum region groups on disk with no cleanup and no 
partition-table
-        // record
-        // (the else branch above never persisted them). Re-submitting on 
recovery is safe instead:
-        // the cleanups are recomputed from the serialized 
failedRegionReplicaSets, each gets a
-        // fresh
-        // procId and performs an idempotent delete, so a duplicate is 
harmless whereas a skip
-        // leaks.
-        removeRegionGroupProcedures.forEach(
-            removeRegionGroupProcedure ->
-                env.getConfigManager()
-                    .getProcedureManager()
-                    .getExecutor()
-                    .submitProcedure(removeRegionGroupProcedure));
         setNextState(CreateRegionGroupsState.REBALANCE_DATA_PARTITION_POLICY);
         break;
       case REBALANCE_DATA_PARTITION_POLICY:
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedure.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedure.java
deleted file mode 100644
index b2b49a688d2..00000000000
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedure.java
+++ /dev/null
@@ -1,389 +0,0 @@
-/*
- * 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.procedure.impl.region;
-
-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.exception.runtime.ThriftSerDeException;
-import org.apache.iotdb.commons.queryengine.utils.DateTimeUtils;
-import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
-import org.apache.iotdb.commons.utils.TestOnly;
-import org.apache.iotdb.commons.utils.ThriftCommonsSerDeUtils;
-import org.apache.iotdb.confignode.i18n.ProcedureMessages;
-import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
-import org.apache.iotdb.confignode.procedure.env.RegionMaintainHandler;
-import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
-import org.apache.iotdb.confignode.procedure.state.RemoveRegionGroupState;
-import org.apache.iotdb.confignode.procedure.store.ProcedureType;
-import org.apache.iotdb.mpp.rpc.thrift.TRegionMigrateResult;
-
-import org.apache.tsfile.utils.ReadWriteIOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-import static org.apache.iotdb.commons.utils.KillPoint.KillPoint.setKillPoint;
-import static 
org.apache.iotdb.confignode.procedure.env.RegionMaintainHandler.simplifiedLocation;
-import static org.apache.iotdb.rpc.TSStatusCode.SUCCESS_STATUS;
-
-/**
- * Delete a whole region group: every replica's consensus peer and all of its 
data on every DataNode
- * that hosts it.
- *
- * <p>Each replica is removed with a local {@code deleteLocalPeer} (the {@code
- * submitDeleteOldRegionPeerTask} path), which needs no consensus quorum and 
tolerates an
- * already-absent peer, so it works for a group of any size — including a 
sub-quorum group that
- * never finished forming. The DataNode runs the deletion asynchronously and 
this procedure polls
- * for the result, so a slow deletion is never wrongly reported as finished.
- *
- * <p>This procedure is submitted as an independent root procedure (not a 
child) by its callers,
- * which only enqueue the deletion and return immediately. It therefore owns 
the deletion end to
- * end: on any failure it retries the current replica forever (backing off 
between attempts) instead
- * of giving up, because there is no parent left to fall back to and the 
region's peer/data must not
- * be left on disk. Each genuine re-attempt uses a FRESH DataNode-side taskId 
(the DataNode dedups
- * by taskId and caches a terminal result forever, so reusing one taskId would 
make every retry a
- * no-op that never re-runs the delete); the in-flight taskId is persisted so 
a leader change
- * re-polls the same task rather than double-submitting. It carries its own 
{@link
- * TRegionReplicaSet} copy, so it can finish even after the caller has dropped 
the partition table,
- * and it survives ConfigNode leader change / restart.
- */
-public class RemoveRegionGroupProcedure extends 
RegionOperationProcedure<RemoveRegionGroupState> {
-  private static final Logger LOGGER = 
LoggerFactory.getLogger(RemoveRegionGroupProcedure.class);
-
-  private static final long DELETE_REPLICA_RETRY_INTERVAL_MS = 5_000;
-
-  private TRegionReplicaSet regionReplicaSet;
-
-  // The index of the replica currently being deleted. Persisted and advanced 
only after that
-  // replica
-  // is deleted, so after a ConfigNode leader change the procedure resumes on 
the first replica it
-  // has
-  // not finished deleting.
-  private int currentReplicaIndex;
-
-  // Number of failed attempts on the replica at currentReplicaIndex, used 
only for logging. Retries
-  // are unbounded, so this is not a budget. Transient: a leader change 
restarts the counter for the
-  // current replica.
-  private transient int attemptedForCurrentReplica;
-
-  // Monotonic count of delete tasks this procedure has submitted, across all 
replicas. Persisted
-  // and
-  // only ever incremented. It is the low half of the DataNode-side taskId 
(see deleteTaskId): a
-  // fresh value per genuine re-attempt makes the DataNode re-run the delete 
instead of replaying a
-  // cached terminal result for a reused taskId (the DataNode dedups by taskId 
and never clears the
-  // cache), which is the bug this fixes. It never resets, so every taskId 
this procedure emits is
-  // distinct even across replicas and retries.
-  private long deleteTaskSeq;
-
-  // Whether a delete task for the replica at currentReplicaIndex has already 
been submitted (and
-  // thus
-  // deleteTaskSeq already identifies an in-flight task to re-poll) rather 
than needing a fresh one.
-  // Persisted so a leader change mid-attempt re-polls the SAME in-flight task 
instead of submitting
-  // a
-  // duplicate; cleared on success or when a terminal failure forces a fresh 
re-attempt.
-  private boolean deleteTaskSubmitted;
-
-  // Bit budget for deleteTaskId(): sign bit (=> negative) + PROC_ID_BITS + 
SEQ_BITS must be <= 64.
-  private static final int SEQ_BITS = 20;
-  private static final int PROC_ID_BITS = 43;
-
-  public RemoveRegionGroupProcedure() {
-    super();
-  }
-
-  public RemoveRegionGroupProcedure(TRegionReplicaSet regionReplicaSet) {
-    super(regionReplicaSet.getRegionId());
-    this.regionReplicaSet = regionReplicaSet;
-  }
-
-  @TestOnly
-  void setCurrentReplicaIndex(int currentReplicaIndex) {
-    this.currentReplicaIndex = currentReplicaIndex;
-  }
-
-  @TestOnly
-  void setDeleteTaskState(long deleteTaskSeq, boolean deleteTaskSubmitted) {
-    this.deleteTaskSeq = deleteTaskSeq;
-    this.deleteTaskSubmitted = deleteTaskSubmitted;
-  }
-
-  @TestOnly
-  long deleteTaskIdForTest() {
-    return deleteTaskId();
-  }
-
-  @Override
-  protected Flow executeFromState(ConfigNodeProcedureEnv env, 
RemoveRegionGroupState state)
-      throws InterruptedException {
-    final List<TDataNodeLocation> dataNodeLocations =
-        regionReplicaSet == null ? null : 
regionReplicaSet.getDataNodeLocations();
-    if (dataNodeLocations == null) {
-      // A null replica set means deserialization failed. Retrying cannot 
recover the lost
-      // locations,
-      // so fail loudly instead of silently reporting the group as deleted 
(which would leave the
-      // region's peer/data on disk with no record of where it lives).
-      setFailure(
-          new ProcedureException(ProcedureMessages.UNSUPPORTED_STATE + 
"missing regionReplicaSet"));
-      return Flow.NO_MORE_STATE;
-    }
-    final RegionMaintainHandler handler = env.getRegionMaintainHandler();
-    switch (state) {
-      case DELETE_REGION_REPLICAS:
-        if (currentReplicaIndex == 0 && attemptedForCurrentReplica == 0) {
-          LOGGER.info(
-              ProcedureMessages.PID_REMOVEREGIONGROUP_STARTED_WILL_BE_DELETED,
-              getProcId(),
-              regionId,
-              dataNodeLocations.stream()
-                  .map(RegionMaintainHandler::simplifiedLocation)
-                  .collect(Collectors.toList()));
-        }
-        if (currentReplicaIndex >= dataNodeLocations.size()) {
-          // Requirement: every successfully completed maintain task must be 
logged.
-          LOGGER.info(
-              ProcedureMessages.PID_REMOVEREGIONGROUP_SUCCESS_PROCEDURE_TOOK,
-              getProcId(),
-              regionId,
-              CommonDateTimeUtils.convertMillisecondToDurationStr(
-                  System.currentTimeMillis() - getSubmittedTime()),
-              DateTimeUtils.convertLongToDate(getSubmittedTime(), "ms"));
-          return Flow.NO_MORE_STATE;
-        }
-
-        final TDataNodeLocation targetDataNode = 
dataNodeLocations.get(currentReplicaIndex);
-        LOGGER.info(
-            
ProcedureMessages.PID_REMOVEREGIONGROUP_STARTED_REPLICA_WILL_BE_DELETED_FROM_DATANODE,
-            getProcId(),
-            regionId,
-            simplifiedLocation(targetDataNode));
-
-        // Start a fresh attempt (fresh taskId) unless we are resuming an 
already-submitted one
-        // after
-        // a leader change, in which case we re-poll the SAME task rather than 
submitting a
-        // duplicate.
-        if (!deleteTaskSubmitted) {
-          deleteTaskSeq++;
-          deleteTaskSubmitted = true;
-        }
-        final long deleteTaskId = deleteTaskId();
-
-        // deleteLocalPeer is idempotent (it tolerates an already-absent 
peer), and re-submitting
-        // the
-        // same taskId re-polls the same DataNode task, so resuming after a 
leader change is safe.
-        final TSStatus submitStatus;
-        final TRegionMigrateResult result;
-        try {
-          submitStatus =
-              handler.submitDeleteOldRegionPeerTask(deleteTaskId, 
targetDataNode, regionId);
-          setKillPoint(state);
-          if (submitStatus.getCode() != SUCCESS_STATUS.getStatusCode()) {
-            return retryCurrentReplica(
-                String.format(
-                    "submit delete task for region %s to DataNode %s failed: 
%s",
-                    regionId, simplifiedLocation(targetDataNode), 
submitStatus));
-          }
-          result = handler.waitTaskFinish(deleteTaskId, targetDataNode);
-        } catch (InterruptedException e) {
-          throw e;
-        } catch (Exception e) {
-          LOGGER.error(ProcedureMessages.PID_REMOVEREGIONGROUP_STATE_FAILED, 
getProcId(), state, e);
-          return retryCurrentReplica(
-              String.format(
-                  "delete region %s from DataNode %s threw %s",
-                  regionId, simplifiedLocation(targetDataNode), e));
-        }
-
-        switch (result.getTaskStatus()) {
-          case SUCCESS:
-            // Advance to the next replica with a fresh retry counter and a 
fresh delete task.
-            currentReplicaIndex++;
-            attemptedForCurrentReplica = 0;
-            deleteTaskSubmitted = false;
-            setNextState(RemoveRegionGroupState.DELETE_REGION_REPLICAS);
-            return Flow.HAS_MORE_STATE;
-          case PROCESSING:
-            // waitTaskFinish() only returns PROCESSING when its polling loop 
was interrupted, i.e.
-            // this ConfigNode is shutting down / losing leadership. The 
delete task is still
-            // running on the DataNode, so persist and re-poll after recovery: 
stay on this replica
-            // without advancing it, without consuming a retry attempt, and 
keeping deleteTaskSeq /
-            // deleteTaskSubmitted so the re-poll targets the same in-flight 
task.
-            setNextState(RemoveRegionGroupState.DELETE_REGION_REPLICAS);
-            return Flow.HAS_MORE_STATE;
-          case TASK_NOT_EXIST:
-          case FAIL:
-          default:
-            return retryCurrentReplica(
-                String.format(
-                    "delete region %s from DataNode %s, task status is %s",
-                    regionId, simplifiedLocation(targetDataNode), 
result.getTaskStatus()));
-        }
-      default:
-        setFailure(new ProcedureException(ProcedureMessages.UNSUPPORTED_STATE 
+ state.name()));
-        return Flow.NO_MORE_STATE;
-    }
-  }
-
-  /**
-   * Retry the replica at {@link #currentReplicaIndex} after a backoff. This 
procedure never gives
-   * up on a replica: because it is submitted as an independent root 
procedure, there is no parent
-   * to fall back to, and skipping or failing would leave the region's 
peer/data on disk. So it
-   * backs off and re-runs the same state until the replica is deleted, which 
eventually succeeds
-   * once the target DataNode is reachable: the delete is idempotent, and 
clearing {@link
-   * #deleteTaskSubmitted} here makes the next attempt use a FRESH 
DataNode-side taskId (a new
-   * {@link #deleteTaskSeq}), so the DataNode actually re-executes the delete 
instead of returning a
-   * cached terminal result for the previous taskId.
-   */
-  private Flow retryCurrentReplica(String reason) throws InterruptedException {
-    attemptedForCurrentReplica++;
-    LOGGER.warn(
-        ProcedureMessages.PID_REMOVEREGIONGROUP_DELETE_REPLICA_FAILED,
-        getProcId(),
-        regionId,
-        attemptedForCurrentReplica,
-        reason);
-    // Force a fresh delete task on the next attempt so the DataNode re-runs 
the delete rather than
-    // replaying a cached FAIL/SUCCESS for this taskId.
-    deleteTaskSubmitted = false;
-    Thread.sleep(DELETE_REPLICA_RETRY_INTERVAL_MS);
-    setNextState(RemoveRegionGroupState.DELETE_REGION_REPLICAS);
-    return Flow.HAS_MORE_STATE;
-  }
-
-  /**
-   * The DataNode-side taskId for the current attempt, derived from this 
procedure's (globally
-   * unique, consensus-replicated) procId and its monotonic {@link 
#deleteTaskSeq}. It is packed
-   * into the NEGATIVE i64 space, which is disjoint from every real procId 
(all {@code >= 0}); other
-   * region-maintain procedures (add/remove peer) use {@code getProcId()} 
directly as the taskId
-   * against the same DataNode task map, so a negative id can never collide 
with theirs. Unlike
-   * minting from the procedure-store id allocator, this needs nothing extra 
replicated: procId is
-   * already replicated and deleteTaskSeq is persisted with this procedure, so 
the taskId is stable
-   * across a leader change and never regresses.
-   *
-   * <p>Layout: sign bit set (=> negative) | {@value PROC_ID_BITS} bits of 
procId | {@value
-   * SEQ_BITS} bits of deleteTaskSeq. The bounds are astronomically beyond any 
real cluster (a
-   * procId needs 2^43 procedures; a single group delete needs 2^20 retries), 
and are asserted
-   * rather than silently wrapped so a violation fails the procedure loudly 
instead of emitting a
-   * colliding id.
-   */
-  private long deleteTaskId() {
-    final long procId = getProcId();
-    if (procId < 0 || procId >= (1L << PROC_ID_BITS) || deleteTaskSeq >= (1L 
<< SEQ_BITS)) {
-      throw new IllegalStateException(
-          String.format(
-              ProcedureMessages
-                  
.EXCEPTION_CANNOT_DERIVE_A_COLLISION_FREE_DELETE_TASKID_PROCID_ARG_DELETETASKSEQ_ARG_EXCEED_THE_ARG_ARG_BIT_BUDGET_015C598D,
-              procId,
-              deleteTaskSeq,
-              PROC_ID_BITS,
-              SEQ_BITS));
-    }
-    return Long.MIN_VALUE | (procId << SEQ_BITS) | deleteTaskSeq;
-  }
-
-  @Override
-  protected void rollbackState(ConfigNodeProcedureEnv env, 
RemoveRegionGroupState state)
-      throws IOException, InterruptedException, ProcedureException {}
-
-  @Override
-  protected RemoveRegionGroupState getState(int stateId) {
-    return RemoveRegionGroupState.values()[stateId];
-  }
-
-  @Override
-  protected int getStateId(RemoveRegionGroupState removeRegionGroupState) {
-    return removeRegionGroupState.ordinal();
-  }
-
-  @Override
-  protected RemoveRegionGroupState getInitialState() {
-    return RemoveRegionGroupState.DELETE_REGION_REPLICAS;
-  }
-
-  @Override
-  public void serialize(DataOutputStream stream) throws IOException {
-    
stream.writeShort(ProcedureType.REMOVE_REGION_GROUP_PROCEDURE.getTypeCode());
-    super.serialize(stream);
-    ThriftCommonsSerDeUtils.serializeTRegionReplicaSet(regionReplicaSet, 
stream);
-    ReadWriteIOUtils.write(currentReplicaIndex, stream);
-    // Persist the delete-task cursor so a leader change re-derives the SAME 
in-flight taskId and
-    // re-polls it (deleteTaskSubmitted == true) instead of submitting a 
duplicate, and so the
-    // monotonic deleteTaskSeq never regresses.
-    ReadWriteIOUtils.write(deleteTaskSeq, stream);
-    ReadWriteIOUtils.write(deleteTaskSubmitted, stream);
-  }
-
-  @Override
-  public void deserialize(ByteBuffer byteBuffer) {
-    super.deserialize(byteBuffer);
-    try {
-      regionReplicaSet = 
ThriftCommonsSerDeUtils.deserializeTRegionReplicaSet(byteBuffer);
-      regionId = regionReplicaSet.getRegionId();
-      currentReplicaIndex = ReadWriteIOUtils.readInt(byteBuffer);
-      // deleteTaskSeq/deleteTaskSubmitted were appended after the first 
version of this procedure.
-      // That first version only ever existed on the unreleased branch that 
added this procedure
-      // (never in a release), but a dev/CI cluster could persist a blob 
without these trailing
-      // fields; tolerate it by defaulting to "no in-flight task" instead of 
reading past the end.
-      if (byteBuffer.hasRemaining()) {
-        deleteTaskSeq = ReadWriteIOUtils.readLong(byteBuffer);
-        deleteTaskSubmitted = ReadWriteIOUtils.readBool(byteBuffer);
-      }
-    } catch (ThriftSerDeException e) {
-      LOGGER.error(ProcedureMessages.ERROR_IN_DESERIALIZE, this.getClass(), e);
-    }
-  }
-
-  @Override
-  public boolean equals(Object obj) {
-    if (!(obj instanceof RemoveRegionGroupProcedure)) {
-      return false;
-    }
-    RemoveRegionGroupProcedure procedure = (RemoveRegionGroupProcedure) obj;
-    return this.currentReplicaIndex == procedure.currentReplicaIndex
-        && this.deleteTaskSeq == procedure.deleteTaskSeq
-        && this.deleteTaskSubmitted == procedure.deleteTaskSubmitted
-        && Objects.equals(this.regionReplicaSet, procedure.regionReplicaSet);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(regionReplicaSet, currentReplicaIndex, deleteTaskSeq, 
deleteTaskSubmitted);
-  }
-
-  @Override
-  public String toString() {
-    return "RemoveRegionGroupProcedure{"
-        + "regionReplicaSet="
-        + regionReplicaSet
-        + ", currentReplicaIndex="
-        + currentReplicaIndex
-        + ", deleteTaskSeq="
-        + deleteTaskSeq
-        + ", deleteTaskSubmitted="
-        + deleteTaskSubmitted
-        + '}';
-  }
-}
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
index 2af7bd7ad69..7a10483a6bf 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/DeleteDatabaseProcedure.java
@@ -19,21 +19,29 @@
 
 package org.apache.iotdb.confignode.procedure.impl.schema;
 
+import org.apache.iotdb.common.rpc.thrift.TConsensusGroupId;
+import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
+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.exception.runtime.ThriftSerDeException;
 import org.apache.iotdb.commons.service.metric.MetricService;
 import org.apache.iotdb.commons.utils.ThriftConfigNodeSerDeUtils;
+import org.apache.iotdb.confignode.client.async.CnToDnAsyncRequestType;
+import 
org.apache.iotdb.confignode.client.async.CnToDnInternalServiceAsyncRequestManager;
+import 
org.apache.iotdb.confignode.client.async.handlers.DataNodeAsyncRequestContext;
 import 
org.apache.iotdb.confignode.consensus.request.write.database.PreDeleteDatabasePlan;
+import 
org.apache.iotdb.confignode.consensus.request.write.region.OfferRegionMaintainTasksPlan;
 import org.apache.iotdb.confignode.i18n.ProcedureMessages;
 import org.apache.iotdb.confignode.manager.partition.PartitionMetrics;
+import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionDeleteTask;
 import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
 import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
 import org.apache.iotdb.confignode.procedure.impl.StateMachineProcedure;
-import 
org.apache.iotdb.confignode.procedure.impl.region.RemoveRegionGroupProcedure;
 import org.apache.iotdb.confignode.procedure.state.schema.DeleteDatabaseState;
 import org.apache.iotdb.confignode.procedure.store.ProcedureType;
 import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
+import org.apache.iotdb.consensus.exception.ConsensusException;
 import org.apache.iotdb.rpc.TSStatusCode;
 
 import org.apache.thrift.TException;
@@ -43,7 +51,10 @@ import org.slf4j.LoggerFactory;
 import java.io.DataOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 public class DeleteDatabaseProcedure
@@ -104,40 +115,86 @@ public class DeleteDatabaseProcedure
               
ProcedureMessages.LOG_DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_ARG_A49A47AC,
               deleteDatabaseSchema.getName());
 
-          // Enqueue deletion of every region group (both schema and data 
regions) of this database.
-          // Each is submitted as an INDEPENDENT root 
RemoveRegionGroupProcedure rather than a
-          // child:
-          // this procedure only submits the deletions and then returns, so it 
can neither wait for
-          // nor be failed/rolled-back by a slow or failing region deletion. 
Each carries its own
-          // copy of the replica set, so the deletion still completes (and 
survives leader change /
-          // restart) even after the next state drops the partition table.
-          //
-          // Submission is intentionally NOT guarded by isStateDeserialized(): 
the executor persists
-          // a procedure at a state BEFORE that state's body has run (it 
advances the state on the
-          // previous cycle, then may stop at the inter-state boundary on a 
leader switch — see
-          // ProcedureExecutor#executeProcedure). So a recovery that lands on 
this state means the
-          // submission has NOT happened yet; skipping it would drop every 
region group's cleanup
-          // while the next state still drops the partition table, orphaning 
the region peers/data
-          // on
-          // disk with no record of where they live. Re-submitting on recovery 
is safe instead:
-          // every RemoveRegionGroupProcedure gets a fresh procId and performs 
an idempotent delete,
-          // so a duplicate is harmless whereas a skip leaks data.
+          // Submit RegionDeleteTasks
+          final OfferRegionMaintainTasksPlan dataRegionDeleteTaskOfferPlan =
+              new OfferRegionMaintainTasksPlan();
           final List<TRegionReplicaSet> regionReplicaSets =
               env.getAllReplicaSets(deleteDatabaseSchema.getName());
+          final List<TRegionReplicaSet> schemaRegionReplicaSets = new 
ArrayList<>();
           regionReplicaSets.forEach(
               regionReplicaSet -> {
                 // Clear heartbeat cache along the way
                 env.getConfigManager()
                     .getLoadManager()
                     
.removeRegionGroupRelatedCache(regionReplicaSet.getRegionId());
-                env.getConfigManager()
-                    .getProcedureManager()
-                    .getExecutor()
-                    .submitProcedure(new 
RemoveRegionGroupProcedure(regionReplicaSet));
+
+                if (regionReplicaSet
+                    .getRegionId()
+                    .getType()
+                    .equals(TConsensusGroupType.SchemaRegion)) {
+                  schemaRegionReplicaSets.add(regionReplicaSet);
+                } else {
+                  regionReplicaSet
+                      .getDataNodeLocations()
+                      .forEach(
+                          targetDataNode ->
+                              
dataRegionDeleteTaskOfferPlan.appendRegionMaintainTask(
+                                  new RegionDeleteTask(
+                                      targetDataNode, 
regionReplicaSet.getRegionId())));
+                }
               });
-          setNextState(DeleteDatabaseState.DELETE_DATABASE_CONFIG);
-          break;
-        case DELETE_DATABASE_CONFIG:
+
+          if 
(!dataRegionDeleteTaskOfferPlan.getRegionMaintainTaskList().isEmpty()) {
+            // submit async data region delete task
+            
env.getConfigManager().getConsensusManager().write(dataRegionDeleteTaskOfferPlan);
+          }
+
+          // try sync delete schemaengine region
+          final DataNodeAsyncRequestContext<TConsensusGroupId, TSStatus> 
asyncClientHandler =
+              new 
DataNodeAsyncRequestContext<>(CnToDnAsyncRequestType.DELETE_REGION);
+          final Map<Integer, RegionDeleteTask> schemaRegionDeleteTaskMap = new 
HashMap<>();
+          int requestIndex = 0;
+          for (final TRegionReplicaSet schemaRegionReplicaSet : 
schemaRegionReplicaSets) {
+            for (final TDataNodeLocation dataNodeLocation :
+                schemaRegionReplicaSet.getDataNodeLocations()) {
+              asyncClientHandler.putRequest(requestIndex, 
schemaRegionReplicaSet.getRegionId());
+              asyncClientHandler.putNodeLocation(requestIndex, 
dataNodeLocation);
+              schemaRegionDeleteTaskMap.put(
+                  requestIndex,
+                  new RegionDeleteTask(dataNodeLocation, 
schemaRegionReplicaSet.getRegionId()));
+              requestIndex++;
+            }
+          }
+          if (!schemaRegionDeleteTaskMap.isEmpty()) {
+            CnToDnInternalServiceAsyncRequestManager.getInstance()
+                .sendAsyncRequestWithRetry(asyncClientHandler);
+            for (final Map.Entry<Integer, TSStatus> entry :
+                asyncClientHandler.getResponseMap().entrySet()) {
+              if (entry.getValue().getCode() == 
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+                LOG.info(
+                    "[DeleteDatabaseProcedure] Successfully delete 
SchemaRegion[{}] on {}",
+                    asyncClientHandler.getRequest(entry.getKey()),
+                    
schemaRegionDeleteTaskMap.get(entry.getKey()).getTargetDataNode());
+                schemaRegionDeleteTaskMap.remove(entry.getKey());
+              } else {
+                LOG.warn(
+                    "[DeleteDatabaseProcedure] Failed to delete 
SchemaRegion[{}] on {}. Submit to async deletion.",
+                    asyncClientHandler.getRequest(entry.getKey()),
+                    
schemaRegionDeleteTaskMap.get(entry.getKey()).getTargetDataNode());
+              }
+            }
+
+            if (!schemaRegionDeleteTaskMap.isEmpty()) {
+              // submit async schemaengine region delete task for failed sync 
execution
+              final OfferRegionMaintainTasksPlan 
schemaRegionDeleteTaskOfferPlan =
+                  new OfferRegionMaintainTasksPlan();
+              schemaRegionDeleteTaskMap
+                  .values()
+                  
.forEach(schemaRegionDeleteTaskOfferPlan::appendRegionMaintainTask);
+              
env.getConfigManager().getConsensusManager().write(schemaRegionDeleteTaskOfferPlan);
+            }
+          }
+
           env.getConfigManager()
               .getLoadManager()
               .clearDataPartitionPolicyTable(deleteDatabaseSchema.getName());
@@ -168,7 +225,7 @@ public class DeleteDatabaseProcedure
                     
ProcedureMessages.DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_FAILED));
           }
       }
-    } catch (final TException | IOException e) {
+    } catch (final ConsensusException | TException | IOException e) {
       if (isRollbackSupported(state)) {
         setFailure(
             new ProcedureException(
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/RemoveRegionGroupState.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/RemoveRegionGroupState.java
deleted file mode 100644
index 9b7d99c8a23..00000000000
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/RemoveRegionGroupState.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.procedure.state;
-
-public enum RemoveRegionGroupState {
-  DELETE_REGION_REPLICAS,
-}
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/schema/DeleteDatabaseState.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/schema/DeleteDatabaseState.java
index c0ad67fe811..cde6b2bdd81 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/schema/DeleteDatabaseState.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/schema/DeleteDatabaseState.java
@@ -22,8 +22,5 @@ package org.apache.iotdb.confignode.procedure.state.schema;
 public enum DeleteDatabaseState {
   PRE_DELETE_DATABASE,
   INVALIDATE_CACHE,
-  DELETE_DATABASE_SCHEMA,
-  // Delete the DatabasePartitionTable and related config after all region 
groups have been deleted
-  // by the RemoveRegionGroupProcedure children spawned in 
DELETE_DATABASE_SCHEMA.
-  DELETE_DATABASE_CONFIG
+  DELETE_DATABASE_SCHEMA
 }
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureFactory.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureFactory.java
index aadc40f310c..165b048b636 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureFactory.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureFactory.java
@@ -43,7 +43,6 @@ import 
org.apache.iotdb.confignode.procedure.impl.region.CreateRegionGroupsProce
 import 
org.apache.iotdb.confignode.procedure.impl.region.NotifyRegionMigrationProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.region.ReconstructRegionProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.region.RegionMigrateProcedure;
-import 
org.apache.iotdb.confignode.procedure.impl.region.RemoveRegionGroupProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.region.RemoveRegionPeerProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.schema.AlterEncodingCompressorProcedure;
 import 
org.apache.iotdb.confignode.procedure.impl.schema.AlterLogicalViewProcedure;
@@ -146,9 +145,6 @@ public class ProcedureFactory implements IProcedureFactory {
       case NOTIFY_REGION_MIGRATION_PROCEDURE:
         procedure = new NotifyRegionMigrationProcedure();
         break;
-      case REMOVE_REGION_GROUP_PROCEDURE:
-        procedure = new RemoveRegionGroupProcedure();
-        break;
       case ALTER_ENCODING_COMPRESSOR_PROCEDURE:
         procedure = new AlterEncodingCompressorProcedure(false);
         break;
@@ -470,8 +466,6 @@ public class ProcedureFactory implements IProcedureFactory {
       return ProcedureType.RECONSTRUCT_REGION_PROCEDURE;
     } else if (procedure instanceof NotifyRegionMigrationProcedure) {
       return ProcedureType.NOTIFY_REGION_MIGRATION_PROCEDURE;
-    } else if (procedure instanceof RemoveRegionGroupProcedure) {
-      return ProcedureType.REMOVE_REGION_GROUP_PROCEDURE;
     } else if (procedure instanceof CreateTriggerProcedure) {
       return ProcedureType.CREATE_TRIGGER_PROCEDURE;
     } else if (procedure instanceof DropTriggerProcedure) {
diff --git 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureType.java
 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureType.java
index c29d1afd672..75299638b60 100644
--- 
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureType.java
+++ 
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureType.java
@@ -41,7 +41,6 @@ public enum ProcedureType {
   ADD_REGION_PEER_PROCEDURE((short) 204),
   REMOVE_REGION_PEER_PROCEDURE((short) 205),
   NOTIFY_REGION_MIGRATION_PROCEDURE((short) 206),
-  REMOVE_REGION_GROUP_PROCEDURE((short) 207),
   @TestOnly
   CREATE_MANY_DATABASES_PROCEDURE((short) 250),
 
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/partition/PartitionManagerRegionMaintainTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/partition/PartitionManagerRegionMaintainTest.java
new file mode 100644
index 00000000000..ae920e43d9c
--- /dev/null
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/partition/PartitionManagerRegionMaintainTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.partition;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainType;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class PartitionManagerRegionMaintainTest {
+
+  @Test
+  public void testCreateRegionCompletedStatus() {
+    assertTrue(
+        PartitionManager.isRegionMaintainTaskCompleted(
+            RegionMaintainType.CREATE, new 
TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode())));
+    assertTrue(
+        PartitionManager.isRegionMaintainTaskCompleted(
+            RegionMaintainType.CREATE,
+            new TSStatus(TSStatusCode.REGION_ALREADY_EXISTS.getStatusCode())));
+    assertFalse(
+        PartitionManager.isRegionMaintainTaskCompleted(
+            RegionMaintainType.CREATE,
+            new TSStatus(TSStatusCode.REGION_NOT_EXIST.getStatusCode())));
+    assertFalse(
+        PartitionManager.isRegionMaintainTaskCompleted(
+            RegionMaintainType.CREATE,
+            new TSStatus(TSStatusCode.CREATE_REGION_ERROR.getStatusCode())));
+  }
+
+  @Test
+  public void testDeleteRegionCompletedStatus() {
+    assertTrue(
+        PartitionManager.isRegionMaintainTaskCompleted(
+            RegionMaintainType.DELETE, new 
TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode())));
+    assertTrue(
+        PartitionManager.isRegionMaintainTaskCompleted(
+            RegionMaintainType.DELETE,
+            new TSStatus(TSStatusCode.REGION_NOT_EXIST.getStatusCode())));
+    assertFalse(
+        PartitionManager.isRegionMaintainTaskCompleted(
+            RegionMaintainType.DELETE,
+            new TSStatus(TSStatusCode.REGION_ALREADY_EXISTS.getStatusCode())));
+    assertFalse(
+        PartitionManager.isRegionMaintainTaskCompleted(
+            RegionMaintainType.DELETE,
+            new TSStatus(TSStatusCode.DELETE_REGION_ERROR.getStatusCode())));
+  }
+}
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
index 8c32378cfcb..afccb0c0eba 100644
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
+++ 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
@@ -40,8 +40,6 @@ import 
org.apache.iotdb.confignode.consensus.response.partition.RegionInfoListRe
 import org.apache.iotdb.confignode.persistence.partition.PartitionInfo;
 import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionCreateTask;
 import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionDeleteTask;
-import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainTask;
-import 
org.apache.iotdb.confignode.persistence.partition.maintainer.RegionMaintainType;
 import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema;
 import org.apache.iotdb.confignode.rpc.thrift.TShowRegionReq;
 
@@ -154,31 +152,6 @@ public class PartitionInfoTest {
     Assert.assertEquals(partitionInfo, partitionInfo1);
   }
 
-  @Test
-  public void testLegacyRegionDeleteTasksAreFiltered() throws TException, 
IOException {
-    // Region deletion is owned by RemoveRegionGroupProcedure; the 
RegionMaintainer queue only
-    // recreates failed replicas. A legacy RegionDeleteTask (offered by an old 
version and replayed
-    // from a consensus log, or carried over in a snapshot) must be dropped 
rather than queued, so
-    // it cannot block the recreation of that region's other replicas.
-
-    // The offer plan mixes two RegionCreateTasks with one legacy 
RegionDeleteTask.
-    
partitionInfo.offerRegionMaintainTasks(generateOfferRegionMaintainTasksPlan());
-
-    // The DELETE task is filtered out at offer time; only the two CREATE 
tasks remain queued.
-    List<RegionMaintainTask> queuedTasks = 
partitionInfo.getRegionMaintainEntryList();
-    Assert.assertEquals(2, queuedTasks.size());
-    for (RegionMaintainTask task : queuedTasks) {
-      Assert.assertEquals(RegionMaintainType.CREATE, task.getType());
-    }
-
-    // A snapshot round-trip keeps the CREATE tasks and never resurrects a 
DELETE task.
-    Assert.assertTrue(partitionInfo.processTakeSnapshot(snapshotDir));
-    PartitionInfo loaded = new PartitionInfo();
-    loaded.processLoadSnapshot(snapshotDir);
-    Assert.assertEquals(partitionInfo, loaded);
-    Assert.assertEquals(2, loaded.getRegionMaintainEntryList().size());
-  }
-
   @Test
   public void testGetRegionType() {
 
diff --git 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedureTest.java
 
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedureTest.java
deleted file mode 100644
index e239d64c12a..00000000000
--- 
a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedureTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.procedure.impl.region;
-
-import org.apache.iotdb.common.rpc.thrift.TConsensusGroupId;
-import org.apache.iotdb.common.rpc.thrift.TConsensusGroupType;
-import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
-import org.apache.iotdb.common.rpc.thrift.TEndPoint;
-import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
-import org.apache.iotdb.confignode.procedure.store.ProcedureFactory;
-
-import org.apache.tsfile.utils.PublicBAOS;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.io.DataOutputStream;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-public class RemoveRegionGroupProcedureTest {
-  @Test
-  public void serDeTest() throws Exception {
-    final TRegionReplicaSet regionReplicaSet =
-        new TRegionReplicaSet(
-            new TConsensusGroupId(TConsensusGroupType.DataRegion, 10),
-            Arrays.asList(
-                new TDataNodeLocation(
-                    1,
-                    new TEndPoint("127.0.0.1", 0),
-                    new TEndPoint("127.0.0.1", 1),
-                    new TEndPoint("127.0.0.1", 2),
-                    new TEndPoint("127.0.0.1", 3),
-                    new TEndPoint("127.0.0.1", 4)),
-                new TDataNodeLocation(
-                    2,
-                    new TEndPoint("127.0.0.1", 10),
-                    new TEndPoint("127.0.0.1", 11),
-                    new TEndPoint("127.0.0.1", 12),
-                    new TEndPoint("127.0.0.1", 13),
-                    new TEndPoint("127.0.0.1", 14))));
-    final RemoveRegionGroupProcedure procedure = new 
RemoveRegionGroupProcedure(regionReplicaSet);
-    // A non-zero cursor so the round-trip actually exercises 
currentReplicaIndex (de)serialization;
-    // equals/hashCode include it, so a dropped/garbled cursor would fail the 
assertion.
-    procedure.setCurrentReplicaIndex(1);
-    // Non-default delete-task cursor so the round-trip exercises 
deleteTaskSeq/deleteTaskSubmitted
-    // too; equals/hashCode include them, so a dropped/garbled value would 
fail the assertion.
-    procedure.setDeleteTaskState(42L, true);
-    try (PublicBAOS byteArrayOutputStream = new PublicBAOS();
-        DataOutputStream outputStream = new 
DataOutputStream(byteArrayOutputStream)) {
-      procedure.serialize(outputStream);
-      ByteBuffer buffer =
-          ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, 
byteArrayOutputStream.size());
-      // Exercises ProcedureType.REMOVE_REGION_GROUP_PROCEDURE + 
ProcedureFactory registration as
-      // well as the procedure's own serialize/deserialize.
-      Assert.assertEquals(procedure, 
ProcedureFactory.getInstance().create(buffer));
-    }
-  }
-
-  @Test
-  public void deleteTaskIdIsNegativeAndUnique() {
-    // The DataNode taskResultMap is keyed only by taskId and is shared with 
add/remove-peer tasks,
-    // which use a procedure's (non-negative) procId directly as the taskId. 
So a delete taskId must
-    // be strictly negative (disjoint from every procId) and distinct per 
(procId, deleteTaskSeq),
-    // otherwise a later peer op could be silently deduped against a lingering 
delete-task entry.
-    final TRegionReplicaSet regionReplicaSet =
-        new TRegionReplicaSet(
-            new TConsensusGroupId(TConsensusGroupType.DataRegion, 1),
-            Arrays.asList(new TDataNodeLocation()));
-    final java.util.Set<Long> seen = new java.util.HashSet<>();
-    for (long procId : new long[] {0L, 1L, 100L, 1L << 20, (1L << 43) - 1}) {
-      for (long seq : new long[] {1L, 2L, 100L, (1L << 20) - 1}) {
-        final RemoveRegionGroupProcedure procedure =
-            new RemoveRegionGroupProcedure(regionReplicaSet);
-        procedure.setProcId(procId);
-        procedure.setDeleteTaskState(seq, true);
-        final long taskId = procedure.deleteTaskIdForTest();
-        Assert.assertTrue("taskId must be negative: " + taskId, taskId < 0);
-        Assert.assertTrue(
-            "taskId must be unique for (" + procId + "," + seq + ")", 
seen.add(taskId));
-      }
-    }
-  }
-}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
index 9f62a18c73a..28d75ba4f49 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
@@ -2824,6 +2824,7 @@ public class DataNodeInternalRPCServiceImpl implements 
IDataNodeRPCService.Iface
     }
     ConsensusGroupId consensusGroupId =
         
ConsensusGroupId.Factory.createFromTConsensusGroupId(tconsensusGroupId);
+    boolean consensusGroupDeleted = true;
     if (consensusGroupId instanceof DataRegionId) {
       try {
         
DataRegionConsensusImpl.getInstance().deleteLocalPeer(consensusGroupId);
@@ -2831,8 +2832,10 @@ public class DataNodeInternalRPCServiceImpl implements 
IDataNodeRPCService.Iface
         if (!(e instanceof ConsensusGroupNotExistException)) {
           return RpcUtils.getStatus(TSStatusCode.DELETE_REGION_ERROR, 
e.getMessage());
         }
+        consensusGroupDeleted = false;
       }
-      return regionManager.deleteDataRegion((DataRegionId) consensusGroupId);
+      return getDeleteRegionStatus(
+          regionManager.deleteDataRegion((DataRegionId) consensusGroupId), 
consensusGroupDeleted);
     } else {
       try {
         
SchemaRegionConsensusImpl.getInstance().deleteLocalPeer(consensusGroupId);
@@ -2840,11 +2843,23 @@ public class DataNodeInternalRPCServiceImpl implements 
IDataNodeRPCService.Iface
         if (!(e instanceof ConsensusGroupNotExistException)) {
           return RpcUtils.getStatus(TSStatusCode.DELETE_REGION_ERROR, 
e.getMessage());
         }
+        consensusGroupDeleted = false;
       }
-      return regionManager.deleteSchemaRegion((SchemaRegionId) 
consensusGroupId);
+      return getDeleteRegionStatus(
+          regionManager.deleteSchemaRegion((SchemaRegionId) consensusGroupId),
+          consensusGroupDeleted);
     }
   }
 
+  private TSStatus getDeleteRegionStatus(
+      TSStatus localRegionStatus, boolean consensusGroupDeleted) {
+    if (consensusGroupDeleted
+        && localRegionStatus.getCode() == 
TSStatusCode.REGION_NOT_EXIST.getStatusCode()) {
+      return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS);
+    }
+    return localRegionStatus;
+  }
+
   @Override
   public TRegionLeaderChangeResp changeRegionLeader(TRegionLeaderChangeReq 
req) {
     LOGGER.info(DataNodeMiscMessages.CHANGE_REGION_LEADER, req);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeRegionManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeRegionManager.java
index 9704ebc7c05..9501c8e677d 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeRegionManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeRegionManager.java
@@ -138,7 +138,7 @@ public class DataNodeRegionManager {
       tsStatus.setMessage(
           String.format(DataNodeMiscMessages.CREATE_SCHEMA_REGION_FAILED_FMT, 
e2.getMessage()));
     } catch (final ConsensusGroupAlreadyExistException e) {
-      tsStatus = new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
+      tsStatus = new 
TSStatus(TSStatusCode.REGION_ALREADY_EXISTS.getStatusCode());
       tsStatus.setMessage(
           String.format(
               DataNodeMiscMessages.SCHEMA_REGION_ALREADY_EXISTS_FMT, 
schemaRegionId.getId()));
@@ -171,7 +171,7 @@ public class DataNodeRegionManager {
       tsStatus.setMessage(
           String.format(DataNodeMiscMessages.CREATE_DATA_REGION_FAILED_FMT, 
e.getMessage()));
     } catch (ConsensusGroupAlreadyExistException e) {
-      tsStatus = new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode());
+      tsStatus = new 
TSStatus(TSStatusCode.REGION_ALREADY_EXISTS.getStatusCode());
       tsStatus.setMessage(
           String.format(DataNodeMiscMessages.DATA_REGION_ALREADY_EXISTS_FMT, 
dataRegionId.getId()));
     } catch (ConsensusException e) {
@@ -208,17 +208,21 @@ public class DataNodeRegionManager {
   }
 
   public TSStatus deleteDataRegion(DataRegionId dataRegionId) {
-    storageEngine.deleteDataRegion(dataRegionId);
-    dataRegionLockMap.remove(dataRegionId);
-    return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS, "Execute 
successfully");
+    TSStatus status = storageEngine.deleteDataRegion(dataRegionId);
+    if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+      dataRegionLockMap.remove(dataRegionId);
+    }
+    return status;
   }
 
   public TSStatus deleteSchemaRegion(SchemaRegionId schemaRegionId) {
     try {
-      schemaEngine.deleteSchemaRegion(schemaRegionId);
+      if (!schemaEngine.deleteSchemaRegion(schemaRegionId)) {
+        return RpcUtils.getStatus(TSStatusCode.REGION_NOT_EXIST);
+      }
       PipeDataNodeAgent.runtime().schemaListener(schemaRegionId).close();
       schemaRegionLockMap.remove(schemaRegionId);
-      return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS, "Execute 
successfully");
+      return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS);
     } catch (MetadataException e) {
       LOGGER.error(DataNodeMiscMessages.METADATA_ERROR, 
IoTDBConstant.GLOBAL_DB_NAME, e);
       return RpcUtils.getStatus(TSStatusCode.METADATA_ERROR, e.getMessage());
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
index 9b634b443a4..ad8042ec703 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/SchemaEngine.java
@@ -336,12 +336,12 @@ public class SchemaEngine {
     return schemaRegion;
   }
 
-  public synchronized void deleteSchemaRegion(SchemaRegionId schemaRegionId)
+  public synchronized boolean deleteSchemaRegion(SchemaRegionId schemaRegionId)
       throws MetadataException {
     ISchemaRegion schemaRegion = schemaRegionMap.get(schemaRegionId);
     if (schemaRegion == null) {
       logger.warn(DataNodeSchemaMessages.SCHEMA_REGION_ALREADY_DELETED, 
schemaRegionId);
-      return;
+      return false;
     }
     schemaRegion.deleteSchemaRegion();
     schemaMetricManager.removeSchemaRegionMetric(schemaRegionId.getId());
@@ -365,6 +365,7 @@ public class SchemaEngine {
         FileUtils.deleteFileOrDirectory(sgDir);
       }
     }
+    return true;
   }
 
   public int getSchemaRegionNumber() {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/RegionMigrateService.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/RegionMigrateService.java
index 69200271ee0..3b22d97880f 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/RegionMigrateService.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/RegionMigrateService.java
@@ -511,19 +511,17 @@ public class RegionMigrateService implements IService {
       // deletePeer: remove the peer from the consensus group
       TSStatus runResult = deletePeer();
       if (isFailed(runResult)) {
-        // A failed delete must NOT fall through to taskSucceed, otherwise the 
ConfigNode would
-        // forget the task while the peer/data is still present.
         taskFail(
             taskId,
             tRegionId,
             originalDataNode,
             TRegionMigrateFailedType.RemoveConsensusGroupFailed,
             runResult);
-        return;
       }
 
       // deleteRegion: delete region data
       runResult = deleteRegion();
+
       if (isFailed(runResult)) {
         taskFail(
             taskId,
@@ -531,7 +529,6 @@ public class RegionMigrateService implements IService {
             originalDataNode,
             TRegionMigrateFailedType.DeleteRegionFailed,
             runResult);
-        return;
       }
 
       taskSucceed(taskId, tRegionId, "DeletePeer");
@@ -551,13 +548,6 @@ public class RegionMigrateService implements IService {
         } else {
           SchemaRegionConsensusImpl.getInstance().deleteLocalPeer(regionId);
         }
-      } catch (ConsensusGroupNotExistException e) {
-        // The peer is already absent (e.g. a retry after a previous attempt 
removed it, or the
-        // region group is being deleted outright). Treat it as success and 
continue to delete data.
-        taskLogger.info(
-            "{}, The local peer of region {} does not exist, skip deleting it",
-            REGION_MIGRATE_PROCESS,
-            regionId);
       } catch (ConsensusException e) {
         String errorMsg =
             String.format(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/StorageEngine.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/StorageEngine.java
index b5e4b08c72e..4a679790dd4 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/StorageEngine.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/StorageEngine.java
@@ -821,9 +821,9 @@ public class StorageEngine implements IService {
     }
   }
 
-  public void deleteDataRegion(DataRegionId regionId) {
+  public TSStatus deleteDataRegion(DataRegionId regionId) {
     if (!dataRegionMap.containsKey(regionId) || 
deletingDataRegionMap.containsKey(regionId)) {
-      return;
+      return RpcUtils.getStatus(TSStatusCode.REGION_NOT_EXIST);
     }
     DataRegion region =
         deletingDataRegionMap.computeIfAbsent(regionId, k -> 
dataRegionMap.remove(regionId));
@@ -880,10 +880,13 @@ public class StorageEngine implements IService {
             region.getDatabaseName(),
             region.getDataRegionIdString(),
             e);
+        return RpcUtils.getStatus(TSStatusCode.DELETE_REGION_ERROR, 
e.getMessage());
       } finally {
         deletingDataRegionMap.remove(regionId);
       }
+      return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS);
     }
+    return RpcUtils.getStatus(TSStatusCode.REGION_NOT_EXIST);
   }
 
   /**
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DataNodeInternalRPCServiceImplTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DataNodeInternalRPCServiceImplTest.java
index c6059d39d04..ff337f3454c 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DataNodeInternalRPCServiceImplTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/service/DataNodeInternalRPCServiceImplTest.java
@@ -57,12 +57,14 @@ import org.apache.iotdb.db.service.DataNode.DataNodeContext;
 import org.apache.iotdb.db.storageengine.dataregion.DataRegion;
 import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
 import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.mpp.rpc.thrift.TCreateSchemaRegionReq;
 import org.apache.iotdb.mpp.rpc.thrift.TDataNodeHeartbeatReq;
 import org.apache.iotdb.mpp.rpc.thrift.TDataNodeHeartbeatResp;
 import org.apache.iotdb.mpp.rpc.thrift.TPlanNode;
 import org.apache.iotdb.mpp.rpc.thrift.TSendBatchPlanNodeReq;
 import org.apache.iotdb.mpp.rpc.thrift.TSendBatchPlanNodeResp;
 import org.apache.iotdb.mpp.rpc.thrift.TSendSinglePlanNodeReq;
+import org.apache.iotdb.rpc.TSStatusCode;
 
 import org.apache.ratis.util.FileUtils;
 import org.apache.tsfile.enums.TSDataType;
@@ -469,6 +471,30 @@ public class DataNodeInternalRPCServiceImplTest {
     Assert.assertTrue(response.getResponses().get(0).accepted);
   }
 
+  @Test
+  public void testRegionOperationRetryReturnsAlreadyCompletedStatus() {
+    TRegionReplicaSet regionReplicaSet = genRegionReplicaSet();
+    regionReplicaSet.setRegionId(new 
TConsensusGroupId(TConsensusGroupType.SchemaRegion, 2));
+    TCreateSchemaRegionReq createReq =
+        new TCreateSchemaRegionReq()
+            .setRegionReplicaSet(regionReplicaSet)
+            .setStorageGroup("root.retry_test");
+
+    Assert.assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+        
dataNodeInternalRPCServiceImpl.createSchemaRegion(createReq).getCode());
+    Assert.assertEquals(
+        TSStatusCode.REGION_ALREADY_EXISTS.getStatusCode(),
+        
dataNodeInternalRPCServiceImpl.createSchemaRegion(createReq).getCode());
+
+    Assert.assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+        
dataNodeInternalRPCServiceImpl.deleteRegion(regionReplicaSet.getRegionId()).getCode());
+    Assert.assertEquals(
+        TSStatusCode.REGION_NOT_EXIST.getStatusCode(),
+        
dataNodeInternalRPCServiceImpl.deleteRegion(regionReplicaSet.getRegionId()).getCode());
+  }
+
   private TRegionReplicaSet genRegionReplicaSet() {
     List<TDataNodeLocation> dataNodeList = new ArrayList<>();
     dataNodeList.add(

Reply via email to