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 3b7df259b8d Use procedures for region create/delete to fix the
delete-region ghost task (#18033)
3b7df259b8d is described below
commit 3b7df259b8dc49827c07c4d755473c99315b2b96
Author: Yongzao <[email protected]>
AuthorDate: Wed Jul 1 00:20:59 2026 +0800
Use procedures for region create/delete to fix the delete-region ghost task
(#18033)
---
.../iotdb/confignode/i18n/ManagerMessages.java | 5 +-
.../iotdb/confignode/i18n/ProcedureMessages.java | 10 +
.../iotdb/confignode/i18n/ManagerMessages.java | 5 +-
.../iotdb/confignode/i18n/ProcedureMessages.java | 10 +
.../client/async/CnToDnAsyncRequestType.java | 1 -
.../CnToDnInternalServiceAsyncRequestManager.java | 5 -
.../iotdb/confignode/manager/ProcedureManager.java | 4 -
.../manager/partition/PartitionManager.java | 330 ++++++++-------------
.../persistence/partition/PartitionInfo.java | 27 +-
.../impl/region/CreateRegionGroupsProcedure.java | 22 +-
.../impl/region/RemoveRegionGroupProcedure.java | 284 ++++++++++++++++++
.../impl/schema/DeleteDatabaseProcedure.java | 94 +-----
...abaseState.java => RemoveRegionGroupState.java} | 8 +-
.../state/schema/DeleteDatabaseState.java | 5 +-
.../procedure/store/ProcedureFactory.java | 6 +
.../confignode/procedure/store/ProcedureType.java | 1 +
.../confignode/persistence/PartitionInfoTest.java | 27 ++
.../region/RemoveRegionGroupProcedureTest.java | 72 +++++
.../iotdb/db/service/RegionMigrateService.java | 12 +-
19 files changed, 608 insertions(+), 320 deletions(-)
diff --git
a/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
b/iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ManagerMessages.java
index 7c8ec7cf282..bd7f7d699f1 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
@@ -62,7 +62,6 @@ 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
=
@@ -424,8 +423,6 @@ 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 =
@@ -508,6 +505,8 @@ 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 f4ae087b159..4d3cea6f8eb 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
@@ -616,6 +616,16 @@ 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 retry. 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 =
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 1008481fdfa..b73d5d2d2c9 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
@@ -62,7 +62,6 @@ 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 =
"检测到来自 DataNode {} 的历史 pipe 完成上报,pipe {}。remainingEventCount: {},
remainingTime: {}, completedDataNodes: {}";
public static final String DETECTED_COMPLETION_OF_PIPE_STATIC_META_REMOVE_IT
=
@@ -419,8 +418,6 @@ 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 =
@@ -503,6 +500,8 @@ 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/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
b/iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java
index 126055a7c57..9602839fae9 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
@@ -614,6 +614,16 @@ 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] 开始,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] started, {} will be migrated from DataNode {} to
{}.";
public static final String PID_MIGRATEREGION_STATE_COMPLETE =
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 d4db2e5c5a6..2d44c214967 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,7 +39,6 @@ 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 4aec391f7af..4048016548a 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,7 +19,6 @@
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;
@@ -143,10 +142,6 @@ 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 7aa1bc5abf7..3d1c860d678 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
@@ -56,7 +56,6 @@ 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.persistence.ProcedureInfo;
import org.apache.iotdb.confignode.procedure.PartitionTableAutoCleaner;
import org.apache.iotdb.confignode.procedure.Procedure;
@@ -318,9 +317,6 @@ 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 0edb0e389dc..2bbd0d05381 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
@@ -81,9 +81,7 @@ 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;
@@ -106,6 +104,7 @@ 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;
@@ -1322,218 +1321,139 @@ public class PartitionManager {
/**
* Called by {@link PartitionManager#regionMaintainer}.
*
- * <p>Periodically maintain the RegionReplicas to be created or deleted
+ * <p>Periodically recreate the failed RegionReplicas offered to the
RegionMaintainer queue.
+ * Region deletion is owned by {@code RemoveRegionGroupProcedure} and is no
longer handled here.
*/
public void maintainRegionReplicas() {
// The consensusManager of configManager may not be fully initialized at
this time
- 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());
- }
- }
- }
-
- if (selectedRegionMaintainTask.isEmpty()) {
- break;
- }
+ if (getConsensusManager() == null || !getConsensusManager().isLeader()) {
+ return;
+ }
- 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 (entry.getValue().getCode()
- ==
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- 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 (entry.getValue().getCode()
- ==
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- 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 (entry.getValue().getCode()
- == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- successfulTask.add(regionIdMap.get(entry.getKey()));
- }
- }
- break;
- }
+ // 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);
+ }
- if (successfulTask.isEmpty()) {
- break;
- }
+ // 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);
+ }
- 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;
- }
- });
- }
+ 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()));
+ }
- // Poll the head entry if success
- try {
- getConsensusManager()
- .write(new
PollSpecificRegionMaintainTaskPlan(successfulTask));
- } catch (ConsensusException e) {
- LOGGER.warn(CONSENSUS_WRITE_ERROR, e);
- }
+ if (successfulRegions.isEmpty()) {
+ break;
+ }
- 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;
- }
- }
- }
+ // 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;
});
+ }
+
+ // 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);
+ }
+
+ 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;
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ 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()));
+ }
+ }
}
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 af9429e5953..d6abb663551 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,6 +64,7 @@ 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;
@@ -232,7 +233,19 @@ public class PartitionInfo implements SnapshotProcessor {
public TSStatus offerRegionMaintainTasks(
OfferRegionMaintainTasksPlan offerRegionMaintainTasksPlan) {
synchronized (regionMaintainTaskList) {
-
regionMaintainTaskList.addAll(offerRegionMaintainTasksPlan.getRegionMaintainTaskList());
+ // 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(
+ "Dropping legacy region-delete task for {} while replaying offer
plan; region deletion is now handled by RemoveRegionGroupProcedure.",
+ task.getRegionId());
+ continue;
+ }
+ regionMaintainTaskList.add(task);
+ }
return RpcUtils.SUCCESS_STATUS;
}
}
@@ -1073,11 +1086,21 @@ public class PartitionInfo implements SnapshotProcessor
{
databasePartitionTables.put(database, databasePartitionTable);
}
- // restore deletedRegionSet
+ // restore the RegionMaintainer queue
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(
+ "Dropping legacy region-delete task for {} while loading
snapshot; region deletion is now handled by RemoveRegionGroupProcedure.",
+ 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 e9cce807e77..cb26afc97dd 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,7 +36,6 @@ 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;
@@ -51,7 +50,9 @@ 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;
@@ -107,6 +108,9 @@ public class CreateRegionGroupsProcedure
case SHUNT_REGION_REPLICAS:
persistPlan = new CreateRegionGroupsPlan();
final OfferRegionMaintainTasksPlan offerPlan = new
OfferRegionMaintainTasksPlan();
+ // RegionGroups that failed to reach a serving quorum are removed via
a child
+ // RemoveRegionGroupProcedure, which deletes every replica that did
get created.
+ final List<RemoveRegionGroupProcedure> removeRegionGroupProcedures =
new ArrayList<>();
// Filter those RegionGroups that created successfully
createRegionGroupsPlan
.getRegionGroupMap()
@@ -154,7 +158,11 @@ public class CreateRegionGroupsProcedure
.CREATEREGIONGROUPS_FAILED_TO_CREATE_SOME_REPLICAS_OF_REGIONGROUP_BUT_THIS,
regionReplicaSet.getRegionId());
} else {
- // The redundant RegionReplicas should be
deleted otherwise
+ // The redundant RegionReplicas (the ones that
did get created) should
+ // be deleted otherwise
+ final TRegionReplicaSet redundantReplicas =
+ new TRegionReplicaSet()
+
.setRegionId(regionReplicaSet.getRegionId());
regionReplicaSet
.getDataNodeLocations()
.forEach(
@@ -162,12 +170,13 @@ public class CreateRegionGroupsProcedure
if (!failedRegionReplicas
.getDataNodeLocations()
.contains(targetDataNode)) {
- RegionDeleteTask deleteTask =
- new RegionDeleteTask(
- targetDataNode,
regionReplicaSet.getRegionId());
-
offerPlan.appendRegionMaintainTask(deleteTask);
+
redundantReplicas.addToDataNodeLocations(targetDataNode);
}
});
+ if (redundantReplicas.getDataNodeLocationsSize()
> 0) {
+ removeRegionGroupProcedures.add(
+ new
RemoveRegionGroupProcedure(redundantReplicas));
+ }
LOGGER.info(
ProcedureMessages
@@ -188,6 +197,7 @@ public class CreateRegionGroupsProcedure
LOGGER.warn(
ConfigNodeMessages.FAILED_IN_THE_WRITE_API_EXECUTING_THE_CONSENSUS_LAYER_DUE,
e);
}
+ removeRegionGroupProcedures.forEach(this::addChildProcedure);
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
new file mode 100644
index 00000000000..2dd2e0d6dd3
--- /dev/null
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedure.java
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+public class RemoveRegionGroupProcedure extends
RegionOperationProcedure<RemoveRegionGroupState> {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(RemoveRegionGroupProcedure.class);
+
+ private static final int MAX_DELETE_REPLICA_RETRY = 3;
+ 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.
Transient: a leader change
+ // restarts the retry budget for the current replica.
+ private transient int attemptedForCurrentReplica;
+
+ public RemoveRegionGroupProcedure() {
+ super();
+ }
+
+ public RemoveRegionGroupProcedure(TRegionReplicaSet regionReplicaSet) {
+ super(regionReplicaSet.getRegionId());
+ this.regionReplicaSet = regionReplicaSet;
+ }
+
+ @TestOnly
+ void setCurrentReplicaIndex(int currentReplicaIndex) {
+ this.currentReplicaIndex = currentReplicaIndex;
+ }
+
+ @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; fail loudly instead
of silently reporting
+ // the group as deleted, otherwise the parent would drop the partition
table while the region
+ // data is still on disk.
+ 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));
+
+ // deleteLocalPeer is idempotent (it tolerates an already-absent peer)
and the DataNode
+ // dedups by taskId, so re-submitting after a leader change or a retry
is safe.
+ final TSStatus submitStatus;
+ final TRegionMigrateResult result;
+ try {
+ submitStatus =
+ handler.submitDeleteOldRegionPeerTask(getProcId(),
targetDataNode, regionId);
+ setKillPoint(state);
+ if (submitStatus.getCode() != SUCCESS_STATUS.getStatusCode()) {
+ return retryCurrentReplicaOrFail(
+ String.format(
+ "submit delete task for region %s to DataNode %s failed:
%s",
+ regionId, simplifiedLocation(targetDataNode),
submitStatus));
+ }
+ result = handler.waitTaskFinish(getProcId(), targetDataNode);
+ } catch (InterruptedException e) {
+ throw e;
+ } catch (Exception e) {
+ LOGGER.error(ProcedureMessages.PID_REMOVEREGIONGROUP_STATE_FAILED,
getProcId(), state, e);
+ return retryCurrentReplicaOrFail(
+ 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 budget.
+ currentReplicaIndex++;
+ attemptedForCurrentReplica = 0;
+ 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 and without consuming a retry attempt.
+ setNextState(RemoveRegionGroupState.DELETE_REGION_REPLICAS);
+ return Flow.HAS_MORE_STATE;
+ case TASK_NOT_EXIST:
+ case FAIL:
+ default:
+ return retryCurrentReplicaOrFail(
+ 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, or
fail the whole procedure
+ * once the per-replica retry budget is exhausted. Failing (rather than
skipping the replica)
+ * keeps the parent from dropping the partition table while a region's
peer/data is still on disk.
+ */
+ private Flow retryCurrentReplicaOrFail(String reason) throws
InterruptedException {
+ attemptedForCurrentReplica++;
+ if (attemptedForCurrentReplica <= MAX_DELETE_REPLICA_RETRY) {
+ LOGGER.warn(
+ ProcedureMessages.PID_REMOVEREGIONGROUP_DELETE_REPLICA_FAILED,
+ getProcId(),
+ regionId,
+ attemptedForCurrentReplica,
+ MAX_DELETE_REPLICA_RETRY + 1,
+ reason);
+ Thread.sleep(DELETE_REPLICA_RETRY_INTERVAL_MS);
+ setNextState(RemoveRegionGroupState.DELETE_REGION_REPLICAS);
+ return Flow.HAS_MORE_STATE;
+ }
+ setFailure(
+ new ProcedureException(
+ String.format(
+ "[pid%d][RemoveRegionGroup] gave up after %d attempts: %s",
+ getProcId(), attemptedForCurrentReplica, reason)));
+ return Flow.NO_MORE_STATE;
+ }
+
+ @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);
+ }
+
+ @Override
+ public void deserialize(ByteBuffer byteBuffer) {
+ super.deserialize(byteBuffer);
+ try {
+ regionReplicaSet =
ThriftCommonsSerDeUtils.deserializeTRegionReplicaSet(byteBuffer);
+ regionId = regionReplicaSet.getRegionId();
+ currentReplicaIndex = ReadWriteIOUtils.readInt(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
+ && Objects.equals(this.regionReplicaSet, procedure.regionReplicaSet);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(regionReplicaSet, currentReplicaIndex);
+ }
+
+ @Override
+ public String toString() {
+ return "RemoveRegionGroupProcedure{"
+ + "regionReplicaSet="
+ + regionReplicaSet
+ + ", currentReplicaIndex="
+ + currentReplicaIndex
+ + '}';
+ }
+}
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 7b8de782173..4b979105c2f 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,29 +19,21 @@
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;
@@ -51,10 +43,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;
public class DeleteDatabaseProcedure
@@ -114,86 +103,23 @@ public class DeleteDatabaseProcedure
"[DeleteDatabaseProcedure] Delete DatabaseSchema: {}",
deleteDatabaseSchema.getName());
- // Submit RegionDeleteTasks
- final OfferRegionMaintainTasksPlan dataRegionDeleteTaskOfferPlan =
- new OfferRegionMaintainTasksPlan();
+ // Delete every region group (both schema and data regions) of this
database via a
+ // RemoveRegionGroupProcedure child. The DatabasePartitionTable
(handled in the next
+ // state) is only removed once these children have finished, so a
slow region deletion is
+ // always completed before the coordinator forgets about it.
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());
-
- if (regionReplicaSet
- .getRegionId()
- .getType()
- .equals(TConsensusGroupType.SchemaRegion)) {
- schemaRegionReplicaSets.add(regionReplicaSet);
- } else {
- regionReplicaSet
- .getDataNodeLocations()
- .forEach(
- targetDataNode ->
-
dataRegionDeleteTaskOfferPlan.appendRegionMaintainTask(
- new RegionDeleteTask(
- targetDataNode,
regionReplicaSet.getRegionId())));
- }
+ addChildProcedure(new
RemoveRegionGroupProcedure(regionReplicaSet));
});
-
- 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);
- }
- }
-
+ setNextState(DeleteDatabaseState.DELETE_DATABASE_CONFIG);
+ break;
+ case DELETE_DATABASE_CONFIG:
env.getConfigManager()
.getLoadManager()
.clearDataPartitionPolicyTable(deleteDatabaseSchema.getName());
@@ -220,7 +146,7 @@ public class DeleteDatabaseProcedure
ProcedureMessages.DELETEDATABASEPROCEDURE_DELETE_DATABASESCHEMA_FAILED));
}
}
- } catch (final ConsensusException | TException | IOException e) {
+ } catch (final TException | IOException e) {
if (isRollbackSupported(state)) {
setFailure(
new ProcedureException(
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/RemoveRegionGroupState.java
similarity index 83%
copy from
iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/schema/DeleteDatabaseState.java
copy to
iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/RemoveRegionGroupState.java
index cde6b2bdd81..9b7d99c8a23 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/RemoveRegionGroupState.java
@@ -17,10 +17,8 @@
* under the License.
*/
-package org.apache.iotdb.confignode.procedure.state.schema;
+package org.apache.iotdb.confignode.procedure.state;
-public enum DeleteDatabaseState {
- PRE_DELETE_DATABASE,
- INVALIDATE_CACHE,
- DELETE_DATABASE_SCHEMA
+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 cde6b2bdd81..c0ad67fe811 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,5 +22,8 @@ package org.apache.iotdb.confignode.procedure.state.schema;
public enum DeleteDatabaseState {
PRE_DELETE_DATABASE,
INVALIDATE_CACHE,
- DELETE_DATABASE_SCHEMA
+ 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
}
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 231b44ad2ee..34f655dfcff 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,6 +43,7 @@ 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;
@@ -145,6 +146,9 @@ 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;
@@ -463,6 +467,8 @@ 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 1cd6a46a4dc..c6b885bfac2 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,6 +41,7 @@ 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/persistence/PartitionInfoTest.java
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/PartitionInfoTest.java
index afccb0c0eba..8c32378cfcb 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,6 +40,8 @@ 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;
@@ -152,6 +154,31 @@ 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
new file mode 100644
index 00000000000..3ca6efaf037
--- /dev/null
+++
b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionGroupProcedureTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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);
+ 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));
+ }
+ }
+}
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 39c1a1678cf..72fc56033b6 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
@@ -500,17 +500,19 @@ 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,
@@ -518,6 +520,7 @@ public class RegionMigrateService implements IService {
originalDataNode,
TRegionMigrateFailedType.DeleteRegionFailed,
runResult);
+ return;
}
taskSucceed(taskId, tRegionId, "DeletePeer");
@@ -537,6 +540,13 @@ 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(