Skip to content

Commit 14b786c

Browse files
committed
Fix stale IoTConsensus peers after restart during removal
Reconcile retained replicas after committing peer removal in ConfigNode. Persist and retry finalization until every retained replica acknowledges. Cover stale-peer restart recovery and procedure timeout scheduling.
1 parent a7b88b1 commit 14b786c

8 files changed

Lines changed: 1324 additions & 4 deletions

File tree

‎iotdb-core/confignode/src/main/i18n/en/org/apache/iotdb/confignode/i18n/ProcedureMessages.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,14 @@ public final class ProcedureMessages {
847847
"RemoveRegionLocation remove region {} from DataNode {}, result is {}";
848848
public static final String REMOVEREGIONPEER_STATE_FAILED = "RemoveRegionPeer state {} failed";
849849
public static final String REMOVEREGIONPEER_STATE_SUCCESS = "RemoveRegionPeer state {} success";
850+
public static final String
851+
LOG_CANNOT_RECONCILE_PEER_LIST_FOR_ARG_AFTER_REMOVING_DATANODE_ARG_THE_CURRENT_REPLICA_SET_STILL_CONTAINS_THE_REMOVED_DATANODE_DBBF45DC =
852+
"Cannot reconcile peer list for {} after removing DataNode {}: the current replica set still contains the removed DataNode.";
853+
public static final String
854+
LOG_FAILED_TO_RESET_PEER_LIST_FOR_ARG_ON_DATANODE_ARG_TO_ARG_ARG_C8D872B4 =
855+
"Failed to reset peer list for {} on DataNode {} to {}: {}";
856+
public static final String LOG_PIDARG_REMOVEREGION_STATE_ARG_WILL_RETRY_IN_ARG_MS_080917A9 =
857+
"[pid{}][RemoveRegion] state {} will retry in {} ms.";
850858
public static final String REMOVEREGION_RATIS_TRANSFER_LEADER_FAIL_BUT_PROCEDURE_WILL_CONTINUE =
851859
"[RemoveRegion] Ratis transfer leader fail, but procedure will continue.";
852860
public static final String REMOVE_CONFIG_NODE = "Remove Config Node";

‎iotdb-core/confignode/src/main/i18n/zh/org/apache/iotdb/confignode/i18n/ProcedureMessages.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,14 @@ public final class ProcedureMessages {
806806
"RemoveRegionLocation:从 DataNode {} 移除 region {},结果为 {}";
807807
public static final String REMOVEREGIONPEER_STATE_FAILED = "RemoveRegionPeer 状态 {} 失败";
808808
public static final String REMOVEREGIONPEER_STATE_SUCCESS = "RemoveRegionPeer 状态 {} 成功";
809+
public static final String
810+
LOG_CANNOT_RECONCILE_PEER_LIST_FOR_ARG_AFTER_REMOVING_DATANODE_ARG_THE_CURRENT_REPLICA_SET_STILL_CONTAINS_THE_REMOVED_DATANODE_DBBF45DC =
811+
"无法纠正 {} 在移除 DataNode {} 后的 peer 列表:当前副本集仍包含已移除的 DataNode。";
812+
public static final String
813+
LOG_FAILED_TO_RESET_PEER_LIST_FOR_ARG_ON_DATANODE_ARG_TO_ARG_ARG_C8D872B4 =
814+
"将 {} 在 DataNode {} 上的 peer 列表重置为 {} 失败:{}";
815+
public static final String LOG_PIDARG_REMOVEREGION_STATE_ARG_WILL_RETRY_IN_ARG_MS_080917A9 =
816+
"[pid{}][RemoveRegion] 状态 {} 将在 {} 毫秒后重试。";
809817
public static final String REMOVEREGION_RATIS_TRANSFER_LEADER_FAIL_BUT_PROCEDURE_WILL_CONTINUE =
810818
"[RemoveRegion] Ratis 转换 leader 失败,但 procedure 将继续。";
811819
public static final String REMOVE_CONFIG_NODE = "移除 ConfigNode";

‎iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/ProcedureExecutor.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,11 @@ private void executeProcedure(RootProcedureStack rootProcStack, Procedure<Env> p
539539
return;
540540
}
541541

542+
if (subprocs == null && proc.getState() == ProcedureState.WAITING_TIMEOUT) {
543+
// Persist the suspended state before a timeout can make the procedure runnable again.
544+
timeoutExecutor.add(proc);
545+
}
546+
542547
if (proc.isRunnable() && proc.isYieldAfterExecution(this.environment)) {
543548
yieldProcedure(proc);
544549
return;

‎iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/env/RegionMaintainHandler.java‎

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,58 @@ public Map<Integer, TSStatus> resetPeerList(
360360
return clientHandler.getResponseMap();
361361
}
362362

363+
public boolean isIoTConsensusDataRegion(TConsensusGroupId regionId) {
364+
return TConsensusGroupType.DataRegion.equals(regionId.getType())
365+
&& IOT_CONSENSUS.equals(CONF.getDataRegionConsensusProtocolClass());
366+
}
367+
368+
/**
369+
* Reconcile retained IoTConsensus replicas after the removed location has been committed. A
370+
* restarting replica may have received the previous peer list while the removal was in progress.
371+
* The existing reset RPC waits for consensus startup before applying this final list.
372+
*/
373+
public boolean resetPeerListAfterRemove(
374+
TConsensusGroupId regionId, TDataNodeLocation removedDataNode) {
375+
if (!isIoTConsensusDataRegion(regionId)) {
376+
return true;
377+
}
378+
Optional<TRegionReplicaSet> replicaSet = getRegionReplicaSet(regionId);
379+
if (!replicaSet.isPresent()) {
380+
// Database deletion may have removed the entire Region from ConfigNode metadata. Its
381+
// deletion tasks own the cleanup, and there are no retained replicas left to reconcile.
382+
return true;
383+
}
384+
if (replicaSet.get().getDataNodeLocations().stream()
385+
.anyMatch(location -> location.getDataNodeId() == removedDataNode.getDataNodeId())) {
386+
LOGGER.warn(
387+
ProcedureMessages
388+
.LOG_CANNOT_RECONCILE_PEER_LIST_FOR_ARG_AFTER_REMOVING_DATANODE_ARG_THE_CURRENT_REPLICA_SET_STILL_CONTAINS_THE_REMOVED_DATANODE_DBBF45DC,
389+
regionId,
390+
removedDataNode.getDataNodeId());
391+
return false;
392+
}
393+
List<TDataNodeLocation> correctLocations = replicaSet.get().getDataNodeLocations();
394+
Map<Integer, TDataNodeLocation> retainedNodes = new HashMap<>();
395+
correctLocations.forEach(location -> retainedNodes.put(location.getDataNodeId(), location));
396+
// Do not filter offline replicas: a missed reset must remain pending until they restart.
397+
Map<Integer, TSStatus> responses = resetPeerList(regionId, correctLocations, retainedNodes);
398+
boolean allSucceeded = true;
399+
for (TDataNodeLocation location : correctLocations) {
400+
TSStatus response = responses == null ? null : responses.get(location.getDataNodeId());
401+
if (response == null || response.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
402+
LOGGER.warn(
403+
ProcedureMessages
404+
.LOG_FAILED_TO_RESET_PEER_LIST_FOR_ARG_ON_DATANODE_ARG_TO_ARG_ARG_C8D872B4,
405+
regionId,
406+
location.getDataNodeId(),
407+
correctLocations,
408+
response);
409+
allSucceeded = false;
410+
}
411+
}
412+
return allSucceeded;
413+
}
414+
363415
// TODO: will use 'procedure yield' to refactor later
364416
public TRegionMigrateResult waitTaskFinish(long taskId, TDataNodeLocation dataNodeLocation) {
365417
return waitTaskFinish(taskId, dataNodeLocation, null);
@@ -449,7 +501,7 @@ public void forceUpdateRegionCache(
449501
.forceUpdateRegionCache(regionId, newLocation.getDataNodeId(), regionStatus);
450502
}
451503

452-
public void removeRegionLocation(
504+
public TSStatus removeRegionLocation(
453505
TConsensusGroupId regionId, TDataNodeLocation deprecatedLocation) {
454506
RemoveRegionLocationPlan req = new RemoveRegionLocationPlan(regionId, deprecatedLocation);
455507
TSStatus status = configManager.getPartitionManager().removeRegionLocation(req);
@@ -458,8 +510,14 @@ public void removeRegionLocation(
458510
regionId,
459511
getIdWithRpcEndpoint(deprecatedLocation),
460512
status);
461-
configManager.getLoadManager().removeRegionCache(regionId, deprecatedLocation.getDataNodeId());
462-
configManager.getLoadManager().getRouteBalancer().balanceRegionLeaderAndPriority();
513+
if (!isIoTConsensusDataRegion(regionId)
514+
|| (status != null && status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode())) {
515+
configManager
516+
.getLoadManager()
517+
.removeRegionCache(regionId, deprecatedLocation.getDataNodeId());
518+
configManager.getLoadManager().getRouteBalancer().balanceRegionLeaderAndPriority();
519+
}
520+
return status;
463521
}
464522

465523
/**

‎iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/region/RemoveRegionPeerProcedure.java‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv;
3333
import org.apache.iotdb.confignode.procedure.env.RegionMaintainHandler;
3434
import org.apache.iotdb.confignode.procedure.exception.ProcedureException;
35+
import org.apache.iotdb.confignode.procedure.state.ProcedureState;
3536
import org.apache.iotdb.confignode.procedure.state.RemoveRegionPeerState;
3637
import org.apache.iotdb.confignode.procedure.store.ProcedureType;
3738
import org.apache.iotdb.mpp.rpc.thrift.TRegionMigrateResult;
@@ -55,6 +56,7 @@ public class RemoveRegionPeerProcedure extends RegionOperationProcedure<RemoveRe
5556
private static final Logger LOGGER = LoggerFactory.getLogger(RemoveRegionPeerProcedure.class);
5657
private static final int MAX_DELETE_OLD_REGION_PEER_RETRY = 3;
5758
private static final long DELETE_OLD_REGION_PEER_RETRY_INTERVAL_MS = 5_000;
59+
private static final long RECONCILE_PEER_LIST_RETRY_INTERVAL_MS = 5_000;
5860
private TDataNodeLocation coordinator;
5961
private TDataNodeLocation targetDataNode;
6062
private transient int deleteOldRegionPeerAttempted = 0;
@@ -91,6 +93,8 @@ protected Flow executeFromState(ConfigNodeProcedureEnv env, RemoveRegionPeerStat
9193
}
9294
TSStatus tsStatus;
9395
RegionMaintainHandler handler = env.getRegionMaintainHandler();
96+
final boolean reconcilePeerList =
97+
state == REMOVE_REGION_LOCATION_CACHE && handler.isIoTConsensusDataRegion(regionId);
9498
try {
9599
switch (state) {
96100
case TRANSFER_REGION_LEADER:
@@ -193,7 +197,13 @@ protected Flow executeFromState(ConfigNodeProcedureEnv env, RemoveRegionPeerStat
193197
setNextState(REMOVE_REGION_LOCATION_CACHE);
194198
break;
195199
case REMOVE_REGION_LOCATION_CACHE:
196-
handler.removeRegionLocation(regionId, targetDataNode);
200+
tsStatus = handler.removeRegionLocation(regionId, targetDataNode);
201+
if (reconcilePeerList
202+
&& (tsStatus == null
203+
|| tsStatus.getCode() != SUCCESS_STATUS.getStatusCode()
204+
|| !handler.resetPeerListAfterRemove(regionId, targetDataNode))) {
205+
return suspendFinalizationForRetry();
206+
}
197207
setKillPoint(state);
198208
LOGGER.info(ProcedureMessages.REMOVEREGIONPEER_STATE_SUCCESS, state);
199209
LOGGER.info(
@@ -211,12 +221,40 @@ protected Flow executeFromState(ConfigNodeProcedureEnv env, RemoveRegionPeerStat
211221
}
212222
} catch (Exception e) {
213223
LOGGER.error(ProcedureMessages.REMOVEREGIONPEER_STATE_FAILED, state, e);
224+
if (reconcilePeerList) {
225+
return suspendFinalizationForRetry();
226+
}
214227
return Flow.NO_MORE_STATE;
215228
}
216229
LOGGER.info(ProcedureMessages.PID_REMOVEREGION_STATE_SUCCESS, getProcId(), state);
217230
return Flow.HAS_MORE_STATE;
218231
}
219232

233+
private Flow suspendFinalizationForRetry() {
234+
// Keep the existing persisted state until every retained replica acknowledges the final list.
235+
// Replaying the metadata removal after a leader switch is idempotent.
236+
setNextState(REMOVE_REGION_LOCATION_CACHE);
237+
setTimeout(RECONCILE_PEER_LIST_RETRY_INTERVAL_MS);
238+
setState(ProcedureState.WAITING_TIMEOUT);
239+
LOGGER.warn(
240+
ProcedureMessages.LOG_PIDARG_REMOVEREGION_STATE_ARG_WILL_RETRY_IN_ARG_MS_080917A9,
241+
getProcId(),
242+
REMOVE_REGION_LOCATION_CACHE,
243+
RECONCILE_PEER_LIST_RETRY_INTERVAL_MS);
244+
return Flow.HAS_MORE_STATE;
245+
}
246+
247+
@Override
248+
protected synchronized boolean setTimeoutFailure(ConfigNodeProcedureEnv env) {
249+
if (getState() == ProcedureState.WAITING_TIMEOUT
250+
&& getCurrentState() == REMOVE_REGION_LOCATION_CACHE) {
251+
setState(ProcedureState.RUNNABLE);
252+
env.getScheduler().addBack(this);
253+
return false;
254+
}
255+
return super.setTimeoutFailure(env);
256+
}
257+
220258
@Override
221259
protected void rollbackState(ConfigNodeProcedureEnv env, RemoveRegionPeerState state)
222260
throws IOException, InterruptedException, ProcedureException {}

0 commit comments

Comments
 (0)