CRZbulabula commented on code in PR #17637:
URL: https://github.com/apache/iotdb/pull/17637#discussion_r3433808772


##########
iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/partition/DataPartitionTableIntegrityCheckProcedure.java:
##########
@@ -1114,4 +1117,86 @@ public void setSkipDataNodes(Set<TDataNodeConfiguration> 
skipDataNodes) {
   public void setFailedDataNodes(Set<TDataNodeConfiguration> failedDataNodes) {
     this.failedDataNodes = failedDataNodes;
   }
+
+  public TShowRepairDataPartitionTableProgressResp getProgress(final 
ConfigNodeProcedureEnv env) {
+    final DataPartitionTableIntegrityCheckProcedureState currentState = 
getCurrentState();
+    final String state = currentState == null ? "UNKNOWN" : 
currentState.name();
+    final double progress =
+        currentState == null ? 0.0 : calculateProgressByState(env, 
currentState) * 100;
+
+    return new TShowRepairDataPartitionTableProgressResp(
+            RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS), state, progress)
+        .setMessage(String.format("DataPartitionTable integrity check 
progress: %.1f%%", progress));
+  }
+
+  private double calculateProgressByState(
+      final ConfigNodeProcedureEnv env,
+      final DataPartitionTableIntegrityCheckProcedureState currentState) {
+    switch (currentState) {
+      case COLLECT_EARLIEST_TIMESLOTS:
+        return 0.0;
+      case ANALYZE_MISSING_PARTITIONS:
+        return 0.05;
+      case REQUEST_PARTITION_TABLES:
+        return 0.1;
+      case REQUEST_PARTITION_TABLES_HEART_BEAT:
+        return 0.1 + 0.8 * calculateDataNodeGeneratorProgress(env);
+      case MERGE_PARTITION_TABLES:
+        return 0.95;
+      case WRITE_PARTITION_TABLE_TO_CONSENSUS:
+        return 0.99;
+      default:
+        return 0.0;
+    }
+  }
+
+  private double calculateDataNodeGeneratorProgress(final 
ConfigNodeProcedureEnv env) {
+    final LoadManager currentLoadManager =
+        loadManager == null ? env.getConfigManager().getLoadManager() : 
loadManager;
+
+    final Set<TDataNodeConfiguration> targetDataNodes = new 
HashSet<>(allDataNodes);

Review Comment:
   **[Thread-safety] Unsynchronized cross-thread read of procedure fields.**
   
   This method runs on the RPC handler thread, but `allDataNodes` is a 
non-`volatile` `ArrayList` that the procedure-executor thread reassigns on 
every step (`allDataNodes = dataNodeManager.getRegisteredDataNodes()`) and 
clears in `rollbackState` (`allDataNodes.clear()`). Copying it via `new 
HashSet<>(allDataNodes)` while another thread structurally modifies or 
reassigns it can throw `ConcurrentModificationException` or observe a 
half-published reference. The same concern applies to 
`skipDataNodes`/`failedDataNodes`, which are reassigned with `= new 
HashSet<>()` in `executeFromState`/`getInitialState`. A transient exception 
here would fail the user's `SHOW ... PROGRESS` even though the repair itself is 
healthy.
   
   Please snapshot these fields under a lock, make them `volatile`, or wrap the 
read so `getProgress` degrades to `UNKNOWN` instead of throwing.



##########
iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/partition/DataPartitionTableIntegrityCheckProcedure.java:
##########
@@ -1114,4 +1117,86 @@ public void setSkipDataNodes(Set<TDataNodeConfiguration> 
skipDataNodes) {
   public void setFailedDataNodes(Set<TDataNodeConfiguration> failedDataNodes) {
     this.failedDataNodes = failedDataNodes;
   }
+
+  public TShowRepairDataPartitionTableProgressResp getProgress(final 
ConfigNodeProcedureEnv env) {
+    final DataPartitionTableIntegrityCheckProcedureState currentState = 
getCurrentState();
+    final String state = currentState == null ? "UNKNOWN" : 
currentState.name();
+    final double progress =
+        currentState == null ? 0.0 : calculateProgressByState(env, 
currentState) * 100;
+
+    return new TShowRepairDataPartitionTableProgressResp(
+            RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS), state, progress)
+        .setMessage(String.format("DataPartitionTable integrity check 
progress: %.1f%%", progress));
+  }
+
+  private double calculateProgressByState(
+      final ConfigNodeProcedureEnv env,
+      final DataPartitionTableIntegrityCheckProcedureState currentState) {
+    switch (currentState) {
+      case COLLECT_EARLIEST_TIMESLOTS:
+        return 0.0;
+      case ANALYZE_MISSING_PARTITIONS:
+        return 0.05;
+      case REQUEST_PARTITION_TABLES:
+        return 0.1;
+      case REQUEST_PARTITION_TABLES_HEART_BEAT:
+        return 0.1 + 0.8 * calculateDataNodeGeneratorProgress(env);
+      case MERGE_PARTITION_TABLES:
+        return 0.95;
+      case WRITE_PARTITION_TABLE_TO_CONSENSUS:
+        return 0.99;
+      default:
+        return 0.0;
+    }
+  }
+
+  private double calculateDataNodeGeneratorProgress(final 
ConfigNodeProcedureEnv env) {
+    final LoadManager currentLoadManager =
+        loadManager == null ? env.getConfigManager().getLoadManager() : 
loadManager;
+
+    final Set<TDataNodeConfiguration> targetDataNodes = new 
HashSet<>(allDataNodes);
+    targetDataNodes.removeAll(skipDataNodes);
+    if (targetDataNodes.isEmpty()) {
+      return dataPartitionTables.isEmpty() ? 0.0 : 1.0;
+    }
+
+    double progressSum = 0.0;
+    for (TDataNodeConfiguration dataNode : targetDataNodes) {
+      final int dataNodeId = dataNode.getLocation().getDataNodeId();
+      if (dataPartitionTables.containsKey(dataNodeId)
+          || failedDataNodes.contains(dataNode)
+          || 
!NodeStatus.Running.equals(currentLoadManager.getNodeStatus(dataNodeId))) {
+        progressSum += 1.0;
+        continue;
+      }
+
+      try {
+        Object response =
+            SyncDataNodeClientPool.getInstance()
+                .sendSyncRequestToDataNodeWithGivenRetry(

Review Comment:
   **[Performance] Synchronous fan-out RPC inside a status query.**
   
   This issues a synchronous RPC with up to `MAX_RETRY_COUNT` (3) retries to 
every target DataNode, sequentially, on the RPC handler thread. On a large 
cluster with one slow/unreachable DataNode, a `SHOW ... PROGRESS` query can 
block for roughly `N × retries × timeout`.
   
   The `REQUEST_PARTITION_TABLES_HEART_BEAT` state already polls per-DataNode 
generation progress — consider caching the latest per-DataNode progress on the 
procedure and reading it here, which avoids the extra fan-out entirely and is 
both faster and race-free. If the synchronous fan-out is kept, please bound or 
parallelize it.



##########
iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/partition/DataPartitionTableIntegrityCheckProcedure.java:
##########
@@ -1114,4 +1117,86 @@ public void setSkipDataNodes(Set<TDataNodeConfiguration> 
skipDataNodes) {
   public void setFailedDataNodes(Set<TDataNodeConfiguration> failedDataNodes) {
     this.failedDataNodes = failedDataNodes;
   }
+
+  public TShowRepairDataPartitionTableProgressResp getProgress(final 
ConfigNodeProcedureEnv env) {
+    final DataPartitionTableIntegrityCheckProcedureState currentState = 
getCurrentState();
+    final String state = currentState == null ? "UNKNOWN" : 
currentState.name();
+    final double progress =
+        currentState == null ? 0.0 : calculateProgressByState(env, 
currentState) * 100;
+
+    return new TShowRepairDataPartitionTableProgressResp(
+            RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS), state, progress)
+        .setMessage(String.format("DataPartitionTable integrity check 
progress: %.1f%%", progress));
+  }
+
+  private double calculateProgressByState(
+      final ConfigNodeProcedureEnv env,
+      final DataPartitionTableIntegrityCheckProcedureState currentState) {
+    switch (currentState) {
+      case COLLECT_EARLIEST_TIMESLOTS:
+        return 0.0;
+      case ANALYZE_MISSING_PARTITIONS:
+        return 0.05;
+      case REQUEST_PARTITION_TABLES:
+        return 0.1;
+      case REQUEST_PARTITION_TABLES_HEART_BEAT:
+        return 0.1 + 0.8 * calculateDataNodeGeneratorProgress(env);
+      case MERGE_PARTITION_TABLES:
+        return 0.95;
+      case WRITE_PARTITION_TABLE_TO_CONSENSUS:
+        return 0.99;
+      default:

Review Comment:
   **[Maintainability] Silent `default` branch.**
   
   The `default` branch maps any unhandled state to `0.0`. If a new 
`DataPartitionTableIntegrityCheckProcedureState` is added later, progress will 
silently report 0% for it. Please add a `LOG.warn` here to catch enum drift.



##########
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java:
##########
@@ -3522,6 +3523,52 @@ public TGenerateDataPartitionTableHeartbeatResp 
generateDataPartitionTableHeartb
     return resp;
   }
 
+  @Override
+  public TGetDataPartitionTableGeneratorProgressResp 
getDataPartitionTableGeneratorProgress() {
+    TGetDataPartitionTableGeneratorProgressResp resp =
+        new TGetDataPartitionTableGeneratorProgressResp();
+
+    if (currentGenerator == null) {
+      resp.setErrorCode(DataPartitionTableGeneratorState.UNKNOWN.getCode());
+      resp.setProgress(0.0);
+      resp.setMessage("No DataPartitionTable generation task found");
+      resp.setStatus(RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS));
+      return resp;
+    }
+
+    switch (currentGenerator.getStatus()) {

Review Comment:
   **[Possible NPE] Re-reading `volatile currentGenerator` without a local 
copy.**
   
   `currentGenerator` is `volatile`, but this method reads it multiple times 
(`getStatus()`, `getProgress()`, `getErrorMessage()`) after the null-check 
above. The generation-with-heartbeat path sets `currentGenerator = null` on 
completion, so a concurrent null-assignment between the null-check and this 
`switch` would NPE. Please capture `final DataPartitionTableGenerator generator 
= currentGenerator;` once at the top and operate on the local.



##########
iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/partition/PartitionManager.java:
##########
@@ -543,6 +544,18 @@ public void 
markDataPartitionTableIntegrityCheckProcedureFinished() {
     dataPartitionTableIntegrityCheckProcedureRunning.set(false);
   }
 
+  public TShowRepairDataPartitionTableProgressResp 
showRepairDataPartitionTableProgress() {
+    return configManager
+        .getProcedureManager()
+        .getUnfinishedDataPartitionTableIntegrityCheckProcedure()
+        .map(procedure -> 
procedure.getProgress(configManager.getProcedureManager().getEnv()))
+        .orElseGet(
+            () ->
+                new TShowRepairDataPartitionTableProgressResp(
+                        RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS), 
"IDLE", 100.0)

Review Comment:
   **[Semantics] `IDLE` returns `progress = 100.0`.**
   
   When no procedure is running, this returns `IDLE` with `progress = 100.0`. 
"Idle at 100%" is ambiguous — a user who has never triggered a repair will see 
100% progress. Suggest returning `0.0` (or a distinct sentinel) for the idle 
case, or at minimum a comment clarifying the semantics.



##########
iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java:
##########
@@ -1180,6 +1181,17 @@ public TSStatus dataPartitionTableIntegrityCheck() {
     return partitionManager.dataPartitionTableIntegrityCheck();
   }
 
+  @Override
+  public TShowRepairDataPartitionTableProgressResp 
showRepairDataPartitionTableProgress() {
+    TSStatus status = confirmLeader();
+    if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+      return new TShowRepairDataPartitionTableProgressResp(status, "UNKNOWN", 
0.0)

Review Comment:
   **[Maintainability] Magic state strings duplicated across modules.**
   
   The `state` string is produced ad hoc in three different places: `"UNKNOWN"` 
here, `"IDLE"` in `PartitionManager.showRepairDataPartitionTableProgress`, and 
the raw enum `name()` in 
`DataPartitionTableIntegrityCheckProcedure.getProgress`. Please define these as 
shared constants or a small enum so the `state` contract is single-sourced and 
testable, rather than relying on matching string literals across modules.



##########
iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/parser/StatementGeneratorTest.java:
##########
@@ -1055,6 +1055,14 @@ public void testCreateView() throws IllegalPathException 
{
     assertEquals(null, stmt.getQueryStatement());
   }
 
+  @Test
+  public void testShowRepairDataPartitionTableProgress() {

Review Comment:
   **[Test coverage] Add an integration test for progress reporting.**
   
   This parser-level test is good, but it's the only coverage. Please add an 
integration test that exercises the feature end-to-end, covering: (a) `SHOW 
REPAIR DATA PARTITION TABLE PROGRESS` while a `REPAIR DATA PARTITION TABLE` 
procedure is running (asserting a 0–100 `Progress(%)` and a non-terminal 
`Status`); (b) the idle case when no procedure exists; and (c) the non-leader / 
`UNKNOWN` branch. This would also guard the concurrency and DataNode-RPC 
fan-out paths flagged in the other comments. Per the repo conventions, scope 
the new IT to only this feature (`-Dit.test=ClassName`) and place it with the 
existing repair-partition ITs.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to