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

jt2594838 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 3fd6e26eb32 fix(consensus): preserve SyncLog retry ordering (#18323)
3fd6e26eb32 is described below

commit 3fd6e26eb32930167e201bde6c67114f2b40f537
Author: Caideyipi <[email protected]>
AuthorDate: Thu Jul 30 10:12:11 2026 +0800

    fix(consensus): preserve SyncLog retry ordering (#18323)
    
    * test(consensus): expose SyncLog retry ordering issue
    
    * fix(consensus): preserve SyncLog retry ordering
    
    ---------
    
    Co-authored-by: Tian Jiang <[email protected]>
---
 .../iotdb/consensus/i18n/IoTConsensusMessages.java |  3 +
 .../iotdb/consensus/i18n/IoTConsensusMessages.java |  3 +
 .../consensus/iot/IoTConsensusServerImpl.java      | 18 ++++-
 .../apache/iotdb/consensus/iot/ReplicateTest.java  | 83 ++++++++++++++++++++++
 4 files changed, 105 insertions(+), 2 deletions(-)

diff --git 
a/iotdb-core/consensus/src/main/i18n/en/org/apache/iotdb/consensus/i18n/IoTConsensusMessages.java
 
b/iotdb-core/consensus/src/main/i18n/en/org/apache/iotdb/consensus/i18n/IoTConsensusMessages.java
index 24efec2dd60..59e3567109b 100644
--- 
a/iotdb-core/consensus/src/main/i18n/en/org/apache/iotdb/consensus/i18n/IoTConsensusMessages.java
+++ 
b/iotdb-core/consensus/src/main/i18n/en/org/apache/iotdb/consensus/i18n/IoTConsensusMessages.java
@@ -259,6 +259,9 @@ public final class IoTConsensusMessages {
   public static final String
       
MESSAGE_SYNC_LOG_REQUEST_WITH_SYNC_INDEX_ARG_WAS_INTERRUPTED_WHILE_WAITING_81B4ABB2
 =
           "SyncLog request with SyncIndex %d was interrupted while waiting";
+  public static final String
+      
MESSAGE_THE_REQUEST_MUST_WAIT_FOR_THE_PREVIOUS_REQUEST_TO_COMPLETE_470849A7 =
+          "The request must wait for the previous request to complete";
 
   // ===================== SyncStatus =====================
 
diff --git 
a/iotdb-core/consensus/src/main/i18n/zh/org/apache/iotdb/consensus/i18n/IoTConsensusMessages.java
 
b/iotdb-core/consensus/src/main/i18n/zh/org/apache/iotdb/consensus/i18n/IoTConsensusMessages.java
index 810ae0a3761..5164b57b9f5 100644
--- 
a/iotdb-core/consensus/src/main/i18n/zh/org/apache/iotdb/consensus/i18n/IoTConsensusMessages.java
+++ 
b/iotdb-core/consensus/src/main/i18n/zh/org/apache/iotdb/consensus/i18n/IoTConsensusMessages.java
@@ -257,6 +257,9 @@ public final class IoTConsensusMessages {
   public static final String
       
MESSAGE_SYNC_LOG_REQUEST_WITH_SYNC_INDEX_ARG_WAS_INTERRUPTED_WHILE_WAITING_81B4ABB2
 =
           "等待期间 SyncLog 请求(SyncIndex %d)被中断";
+  public static final String
+      
MESSAGE_THE_REQUEST_MUST_WAIT_FOR_THE_PREVIOUS_REQUEST_TO_COMPLETE_470849A7 =
+          "该请求必须等待前置请求完成";
 
   // ===================== SyncStatus =====================
 
diff --git 
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensusServerImpl.java
 
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensusServerImpl.java
index 430507a4048..b0bf44decb6 100644
--- 
a/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensusServerImpl.java
+++ 
b/iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/iot/IoTConsensusServerImpl.java
@@ -35,6 +35,7 @@ import 
org.apache.iotdb.commons.service.metric.PerformanceOverviewMetrics;
 import org.apache.iotdb.commons.utils.CommonDateTimeUtils;
 import org.apache.iotdb.commons.utils.KillPoint.DataNodeKillPoints;
 import org.apache.iotdb.commons.utils.KillPoint.KillPoint;
+import org.apache.iotdb.commons.utils.RetryUtils;
 import org.apache.iotdb.consensus.IStateMachine;
 import org.apache.iotdb.consensus.common.DataSet;
 import org.apache.iotdb.consensus.common.Peer;
@@ -1453,9 +1454,22 @@ public class IoTConsensusServerImpl {
         long sortTime = System.nanoTime();
         ioTConsensusServerMetrics.recordSortCost(sortTime - insertStartTime);
         List<TSStatus> subStatus = new LinkedList<>();
-        for (IConsensusRequest insertNode : request.getInsertNodes()) {
+        List<IConsensusRequest> insertNodes = request.getInsertNodes();
+        for (int i = 0; i < insertNodes.size(); i++) {
+          IConsensusRequest insertNode = insertNodes.get(i);
           insertNode.markAsGeneratedByRemoteConsensusLeader();
-          subStatus.add(stateMachine.write(insertNode));
+          TSStatus status = stateMachine.write(insertNode);
+          subStatus.add(status);
+          if (RetryUtils.needRetryForWrite(status.getCode())) {
+            for (int j = i + 1; j < insertNodes.size(); j++) {
+              subStatus.add(
+                  RpcUtils.getStatus(
+                      TSStatusCode.WRITE_PROCESS_REJECT,
+                      IoTConsensusMessages
+                          
.MESSAGE_THE_REQUEST_MUST_WAIT_FOR_THE_PREVIOUS_REQUEST_TO_COMPLETE_470849A7));
+            }
+            break;
+          }
         }
         if (subStatus.stream()
             .allMatch(status -> status.getCode() == 
TSStatusCode.SUCCESS_STATUS.getStatusCode())) {
diff --git 
a/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/iot/ReplicateTest.java
 
b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/iot/ReplicateTest.java
index e096f559773..fad1fbea334 100644
--- 
a/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/iot/ReplicateTest.java
+++ 
b/iotdb-core/consensus/src/test/java/org/apache/iotdb/consensus/iot/ReplicateTest.java
@@ -26,6 +26,7 @@ import org.apache.iotdb.commons.consensus.ConsensusGroupId;
 import org.apache.iotdb.commons.consensus.DataRegionId;
 import org.apache.iotdb.commons.exception.StartupException;
 import org.apache.iotdb.commons.request.IConsensusRequest;
+import org.apache.iotdb.commons.utils.RetryUtils;
 import org.apache.iotdb.consensus.ConsensusFactory;
 import org.apache.iotdb.consensus.common.ConsensusGroup;
 import org.apache.iotdb.consensus.common.Peer;
@@ -402,6 +403,60 @@ public class ReplicateTest {
     Assert.assertTrue(failingStateMachine.getData().contains(entry));
   }
 
+  /**
+   * Verifies that retrying a SyncLog batch after a transient write failure 
preserves the original
+   * application order on the follower.
+   */
+  @Test
+  public void syncLogWriteProcessErrorRetryPreservesRequestOrderTest() throws 
Exception {
+    IoTConsensusConfig retryConfig =
+        IoTConsensusConfig.newBuilder()
+            .setReplication(
+                
IoTConsensusConfig.Replication.newBuilder().setMaxPendingBatchesNum(1).build())
+            .build();
+    servers
+        .get(0)
+        .reloadConsensusConfig(
+            
ConsensusConfig.newBuilder().setIoTConsensusConfig(retryConfig).build());
+
+    OrderTrackingWriteProcessErrorOnceTestStateMachine failingStateMachine =
+        new OrderTrackingWriteProcessErrorOnceTestStateMachine();
+    stateMachines.set(0, failingStateMachine);
+    servers.get(0).createLocalPeer(group.getGroupId(), group.getPeers());
+    IoTConsensusServerImpl server = servers.get(0).getImpl(gid);
+
+    DeserializedBatchIndexedConsensusRequest request =
+        new DeserializedBatchIndexedConsensusRequest(1, 2, 2, 
peers.get(1).getNodeId(), 2);
+    request.add(
+        new IndexedConsensusRequest(
+            1, 1, Collections.singletonList(new TestEntry(1, peers.get(1)))));
+    request.add(
+        new IndexedConsensusRequest(
+            2, 2, Collections.singletonList(new TestEntry(2, peers.get(1)))));
+
+    TSStatus firstAttempt = server.syncLog(peers.get(1).getNodeId(), request);
+    Assert.assertEquals(
+        TSStatusCode.WRITE_PROCESS_ERROR.getStatusCode(),
+        firstAttempt.getSubStatus().get(0).getCode());
+    Assert.assertEquals(
+        TSStatusCode.WRITE_PROCESS_REJECT.getStatusCode(),
+        firstAttempt.getSubStatus().get(1).getCode());
+    Assert.assertEquals(
+        IoTConsensusMessages
+            
.MESSAGE_THE_REQUEST_MUST_WAIT_FOR_THE_PREVIOUS_REQUEST_TO_COMPLETE_470849A7,
+        firstAttempt.getSubStatus().get(1).getMessage());
+    Assert.assertTrue(
+        firstAttempt.getSubStatus().stream()
+            .anyMatch(status -> 
RetryUtils.needRetryForWrite(status.getCode())));
+
+    TSStatus retryAttempt = server.syncLog(peers.get(1).getNodeId(), request);
+    Assert.assertTrue(
+        retryAttempt.getSubStatus().stream()
+            .allMatch(status -> status.getCode() == 
TSStatusCode.SUCCESS_STATUS.getStatusCode()));
+    Assert.assertEquals(
+        Arrays.asList(1L, 2L), 
failingStateMachine.getFirstSuccessfullyAppliedSyncIndexes());
+  }
+
   @Test
   public void parsingAndConstructIDTest() throws Exception {
     logger.info("Start ParsingAndConstructIDTest");
@@ -453,4 +508,32 @@ public class ReplicateTest {
       return writeAttempts.get();
     }
   }
+
+  private static class OrderTrackingWriteProcessErrorOnceTestStateMachine
+      extends WriteProcessErrorOnceTestStateMachine {
+
+    private final List<Long> firstSuccessfullyAppliedSyncIndexes =
+        Collections.synchronizedList(new ArrayList<>());
+
+    @Override
+    public TSStatus write(IConsensusRequest request) {
+      TSStatus status = super.write(request);
+      if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()
+          && request instanceof IndexedConsensusRequest) {
+        long syncIndex = ((IndexedConsensusRequest) request).getSyncIndex();
+        synchronized (firstSuccessfullyAppliedSyncIndexes) {
+          if (!firstSuccessfullyAppliedSyncIndexes.contains(syncIndex)) {
+            firstSuccessfullyAppliedSyncIndexes.add(syncIndex);
+          }
+        }
+      }
+      return status;
+    }
+
+    private List<Long> getFirstSuccessfullyAppliedSyncIndexes() {
+      synchronized (firstSuccessfullyAppliedSyncIndexes) {
+        return new ArrayList<>(firstSuccessfullyAppliedSyncIndexes);
+      }
+    }
+  }
 }

Reply via email to