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

jiangtian pushed a commit to branch expr
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/expr by this push:
     new 2cc6cd0  add leader changed response
2cc6cd0 is described below

commit 2cc6cd0a42a1d245b60c2d89041e303cc0f203a1
Author: jt <[email protected]>
AuthorDate: Thu Dec 2 09:27:15 2021 +0800

    add leader changed response
---
 .../cluster/log/appender/BlockingLogAppender.java  |  16 +-
 .../iotdb/cluster/log/appender/LogAppender.java    |   7 +-
 .../cluster/log/appender/LogAppenderFactory.java   |   1 -
 .../log/appender/SlidingWindowLogAppender.java     |   5 +-
 .../cluster/server/member/DataGroupMember.java     |  14 ++
 .../iotdb/cluster/server/member/RaftMember.java    | 171 ++++++++-----------
 .../apache/iotdb/db/qp/physical/PhysicalPlan.java  |  11 ++
 .../org/apache/iotdb/db/service/TSServiceImpl.java |  15 +-
 .../java/org/apache/iotdb/rpc/TSStatusCode.java    |   2 +
 .../apache/iotdb/session/SessionConnection.java    | 181 ++++++++++++++++++---
 thrift/src/main/thrift/rpc.thrift                  |  11 ++
 11 files changed, 294 insertions(+), 140 deletions(-)

diff --git 
a/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/BlockingLogAppender.java
 
b/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/BlockingLogAppender.java
index e93969c..7cb614a 100644
--- 
a/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/BlockingLogAppender.java
+++ 
b/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/BlockingLogAppender.java
@@ -19,7 +19,6 @@
 
 package org.apache.iotdb.cluster.log.appender;
 
-import java.util.List;
 import org.apache.iotdb.cluster.config.ClusterConstant;
 import org.apache.iotdb.cluster.log.Log;
 import org.apache.iotdb.cluster.log.manage.RaftLogManager;
@@ -27,9 +26,12 @@ import org.apache.iotdb.cluster.rpc.thrift.AppendEntryResult;
 import org.apache.iotdb.cluster.server.Response;
 import org.apache.iotdb.cluster.server.member.RaftMember;
 import org.apache.iotdb.cluster.server.monitor.Timer;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
+
 /**
  * BlockingLogAppender wait for a certain amount of time when it receives 
out-of-order entries
  * (entries with indices larger than local last entry's index + 1), if the 
local log is updated
@@ -53,7 +55,7 @@ public class BlockingLogAppender implements LogAppender {
    * and append "log" to it. Otherwise report a log mismatch.
    *
    * @return Response.RESPONSE_AGREE when the log is successfully appended or 
Response
-   * .RESPONSE_LOG_MISMATCH if the previous log of "log" is not found.
+   *     .RESPONSE_LOG_MISMATCH if the previous log of "log" is not found.
    */
   public AppendEntryResult appendEntry(
       long prevLogIndex, long prevLogTerm, long leaderCommit, Log log) {
@@ -83,9 +85,7 @@ public class BlockingLogAppender implements LogAppender {
     return result;
   }
 
-  /**
-   * Wait until all logs before "prevLogIndex" arrive or a timeout is reached.
-   */
+  /** Wait until all logs before "prevLogIndex" arrive or a timeout is 
reached. */
   private boolean waitForPrevLog(long prevLogIndex) {
     long waitStart = System.currentTimeMillis();
     long alreadyWait = 0;
@@ -132,7 +132,7 @@ public class BlockingLogAppender implements LogAppender {
    *
    * @param logs append logs
    * @return Response.RESPONSE_AGREE when the log is successfully appended or 
Response
-   * .RESPONSE_LOG_MISMATCH if the previous log of "log" is not found.
+   *     .RESPONSE_LOG_MISMATCH if the previous log of "log" is not found.
    */
   public AppendEntryResult appendEntries(
       long prevLogIndex, long prevLogTerm, long leaderCommit, List<Log> logs) {
@@ -158,8 +158,8 @@ public class BlockingLogAppender implements LogAppender {
       
Timer.Statistic.RAFT_RECEIVER_APPEND_ENTRY.calOperationCostTimeFromStart(startTime);
       if (resp != -1) {
         if (logger.isDebugEnabled()) {
-          logger.debug("{} append a new log list {}, commit to {}", 
member.getName(), logs,
-              leaderCommit);
+          logger.debug(
+              "{} append a new log list {}, commit to {}", member.getName(), 
logs, leaderCommit);
         }
         result.status = Response.RESPONSE_STRONG_ACCEPT;
         result.setLastLogIndex(logManager.getLastLogIndex());
diff --git 
a/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/LogAppender.java 
b/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/LogAppender.java
index f997b06..b5bd35f 100644
--- 
a/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/LogAppender.java
+++ 
b/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/LogAppender.java
@@ -17,13 +17,13 @@
  * under the License.
  */
 
-
 package org.apache.iotdb.cluster.log.appender;
 
-import java.util.List;
 import org.apache.iotdb.cluster.log.Log;
 import org.apache.iotdb.cluster.rpc.thrift.AppendEntryResult;
 
+import java.util.List;
+
 /**
  * LogAppender appends newly incoming entries to the local log of a member, 
providing different
  * policies for out-of-order entries and other cases.
@@ -33,6 +33,5 @@ public interface LogAppender {
   AppendEntryResult appendEntries(
       long prevLogIndex, long prevLogTerm, long leaderCommit, List<Log> logs);
 
-  AppendEntryResult appendEntry(
-      long prevLogIndex, long prevLogTerm, long leaderCommit, Log log);
+  AppendEntryResult appendEntry(long prevLogIndex, long prevLogTerm, long 
leaderCommit, Log log);
 }
diff --git 
a/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/LogAppenderFactory.java
 
b/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/LogAppenderFactory.java
index 1f1e83c..0b4a4d4 100644
--- 
a/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/LogAppenderFactory.java
+++ 
b/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/LogAppenderFactory.java
@@ -17,7 +17,6 @@
  * under the License.
  */
 
-
 package org.apache.iotdb.cluster.log.appender;
 
 import org.apache.iotdb.cluster.server.member.RaftMember;
diff --git 
a/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/SlidingWindowLogAppender.java
 
b/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/SlidingWindowLogAppender.java
index 928f960..d2ecb1c 100644
--- 
a/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/SlidingWindowLogAppender.java
+++ 
b/cluster/src/main/java/org/apache/iotdb/cluster/log/appender/SlidingWindowLogAppender.java
@@ -19,8 +19,6 @@
 
 package org.apache.iotdb.cluster.log.appender;
 
-import java.util.Arrays;
-import java.util.List;
 import org.apache.iotdb.cluster.config.ClusterDescriptor;
 import org.apache.iotdb.cluster.log.Log;
 import org.apache.iotdb.cluster.log.manage.RaftLogManager;
@@ -30,6 +28,9 @@ import org.apache.iotdb.cluster.server.member.RaftMember;
 import org.apache.iotdb.cluster.server.monitor.Timer;
 import org.apache.iotdb.cluster.server.monitor.Timer.Statistic;
 
+import java.util.Arrays;
+import java.util.List;
+
 public class SlidingWindowLogAppender implements LogAppender {
 
   private int windowCapacity =
diff --git 
a/cluster/src/main/java/org/apache/iotdb/cluster/server/member/DataGroupMember.java
 
b/cluster/src/main/java/org/apache/iotdb/cluster/server/member/DataGroupMember.java
index 6472835..975bba2 100644
--- 
a/cluster/src/main/java/org/apache/iotdb/cluster/server/member/DataGroupMember.java
+++ 
b/cluster/src/main/java/org/apache/iotdb/cluster/server/member/DataGroupMember.java
@@ -32,6 +32,8 @@ import org.apache.iotdb.cluster.log.LogApplier;
 import org.apache.iotdb.cluster.log.LogParser;
 import org.apache.iotdb.cluster.log.Snapshot;
 import org.apache.iotdb.cluster.log.VotingLog;
+import org.apache.iotdb.cluster.log.appender.BlockingLogAppender;
+import org.apache.iotdb.cluster.log.appender.SlidingWindowLogAppender;
 import org.apache.iotdb.cluster.log.applier.AsyncDataLogApplier;
 import org.apache.iotdb.cluster.log.applier.DataLogApplier;
 import org.apache.iotdb.cluster.log.logtypes.AddNodeLog;
@@ -192,6 +194,10 @@ public class DataGroupMember extends RaftMember implements 
DataGroupMemberMBean
     setQueryManager(new ClusterQueryManager());
     localQueryExecutor = new LocalQueryExecutor(this);
     lastAppliedPartitionTableVersion = new 
LastAppliedPatitionTableVersion(getMemberDir());
+    appenderFactory =
+        
ClusterDescriptor.getInstance().getConfig().isUseFollowerSlidingWindow()
+            ? new SlidingWindowLogAppender.Factory()
+            : new BlockingLogAppender.Factory();
   }
 
   DataGroupMember(TProtocolFactory factory, PartitionGroup nodes, 
MetaGroupMember metaGroupMember) {
@@ -232,6 +238,10 @@ public class DataGroupMember extends RaftMember implements 
DataGroupMemberMBean
     voteFor = logManager.getHardState().getVoteFor();
     localQueryExecutor = new LocalQueryExecutor(this);
     lastAppliedPartitionTableVersion = new 
LastAppliedPatitionTableVersion(getMemberDir());
+    appenderFactory =
+        
ClusterDescriptor.getInstance().getConfig().isUseFollowerSlidingWindow()
+            ? new SlidingWindowLogAppender.Factory()
+            : new BlockingLogAppender.Factory();
   }
 
   /**
@@ -801,6 +811,10 @@ public class DataGroupMember extends RaftMember implements 
DataGroupMemberMBean
 
   private TSStatus executeNonQueryPlanWithKnownLeader(PhysicalPlan plan) {
     if (character == NodeCharacter.LEADER) {
+      if (plan.getTargetedTerm() > 0 && plan.getTargetedTerm() != term.get()) {
+        return 
StatusUtils.getStatus(TSStatusCode.LEADER_CHANGED).setMessage(term.get() + "");
+      }
+
       long startTime = 
Statistic.DATA_GROUP_MEMBER_LOCAL_EXECUTION.getOperationStartTime();
       TSStatus status = processPlanLocally(plan);
       boolean hasCreated = false;
diff --git 
a/cluster/src/main/java/org/apache/iotdb/cluster/server/member/RaftMember.java 
b/cluster/src/main/java/org/apache/iotdb/cluster/server/member/RaftMember.java
index 4d4587d..ce8579a 100644
--- 
a/cluster/src/main/java/org/apache/iotdb/cluster/server/member/RaftMember.java
+++ 
b/cluster/src/main/java/org/apache/iotdb/cluster/server/member/RaftMember.java
@@ -44,7 +44,6 @@ import org.apache.iotdb.cluster.log.VotingLogList;
 import org.apache.iotdb.cluster.log.appender.BlockingLogAppender;
 import org.apache.iotdb.cluster.log.appender.LogAppender;
 import org.apache.iotdb.cluster.log.appender.LogAppenderFactory;
-import org.apache.iotdb.cluster.log.appender.SlidingWindowLogAppender;
 import org.apache.iotdb.cluster.log.catchup.CatchUpTask;
 import org.apache.iotdb.cluster.log.logtypes.PhysicalPlanLog;
 import org.apache.iotdb.cluster.log.manage.RaftLogManager;
@@ -93,6 +92,7 @@ import 
org.apache.iotdb.db.exception.metadata.StorageGroupNotSetException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
 import org.apache.iotdb.db.qp.executor.PlanExecutor;
 import org.apache.iotdb.db.qp.physical.PhysicalPlan;
+import org.apache.iotdb.db.qp.physical.crud.InsertPlan;
 import org.apache.iotdb.db.qp.physical.sys.LogPlan;
 import org.apache.iotdb.db.utils.TestOnly;
 import org.apache.iotdb.rpc.RpcUtils;
@@ -130,8 +130,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import static 
org.apache.iotdb.cluster.config.ClusterConstant.THREAD_POLL_WAIT_TERMINATION_TIME_S;
 
 /**
- * RaftMember process the common raft logic like leader election, log 
appending, catch-up and so
- * on.
+ * RaftMember process the common raft logic like leader election, log 
appending, catch-up and so on.
  */
 @SuppressWarnings("java:S3077") // reference volatile is enough
 public abstract class RaftMember implements RaftMemberMBean {
@@ -140,12 +139,10 @@ public abstract class RaftMember implements 
RaftMemberMBean {
   public static boolean USE_LOG_DISPATCHER = false;
   private static final boolean USE_INDIRECT_LOG_DISPATCHER =
       ClusterDescriptor.getInstance().getConfig().isUseIndirectBroadcasting();
-  private static final boolean ENABLE_WEAK_ACCEPTANCE = 
ClusterDescriptor.getInstance().getConfig()
-      .isEnableWeakAcceptance();
+  private static final boolean ENABLE_WEAK_ACCEPTANCE =
+      ClusterDescriptor.getInstance().getConfig().isEnableWeakAcceptance();
 
-  private static final LogAppenderFactory APPENDER_FACTORY =
-      ClusterDescriptor.getInstance().getConfig().isUseFollowerSlidingWindow() 
?
-          new SlidingWindowLogAppender.Factory() : new 
BlockingLogAppender.Factory();
+  protected LogAppenderFactory appenderFactory = new 
BlockingLogAppender.Factory();
   protected static final LogSequencerFactory SEQUENCER_FACTORY =
       ClusterDescriptor.getInstance().getConfig().isUseAsyncSequencing()
           ? new Factory()
@@ -169,32 +166,22 @@ public abstract class RaftMember implements 
RaftMemberMBean {
    * on this may be woken.
    */
   private final Object waitLeaderCondition = new Object();
-  /**
-   * the lock is to make sure that only one thread can apply snapshot at the 
same time
-   */
+  /** the lock is to make sure that only one thread can apply snapshot at the 
same time */
   private final Object snapshotApplyLock = new Object();
 
   private final Object heartBeatWaitObject = new Object();
 
   protected Node thisNode = ClusterIoTDB.getInstance().getThisNode();
 
-  /**
-   * the nodes that belong to the same raft group as thisNode.
-   */
+  /** the nodes that belong to the same raft group as thisNode. */
   protected PartitionGroup allNodes;
 
   ClusterConfig config = ClusterDescriptor.getInstance().getConfig();
-  /**
-   * the name of the member, to distinguish several members in the logs.
-   */
+  /** the name of the member, to distinguish several members in the logs. */
   String name;
-  /**
-   * to choose nodes to send request of joining cluster randomly.
-   */
+  /** to choose nodes to send request of joining cluster randomly. */
   Random random = new Random();
-  /**
-   * when the node is a leader, this map is used to track log progress of each 
follower.
-   */
+  /** when the node is a leader, this map is used to track log progress of 
each follower. */
   Map<Node, Peer> peerMap;
   /**
    * the current term of the node, this object also works as lock of some 
transactions of the member
@@ -216,9 +203,7 @@ public abstract class RaftMember implements RaftMemberMBean 
{
    */
   volatile long lastHeartbeatReceivedTime;
 
-  /**
-   * the raft logs are all stored and maintained in the log manager
-   */
+  /** the raft logs are all stored and maintained in the log manager */
   protected RaftLogManager logManager;
 
   /**
@@ -237,9 +222,7 @@ public abstract class RaftMember implements RaftMemberMBean 
{
    * member by comparing it with the current last log index.
    */
   long lastReportedLogIndex;
-  /**
-   * the thread pool that runs catch-up tasks
-   */
+  /** the thread pool that runs catch-up tasks */
   private ExecutorService catchUpService;
   /**
    * lastCatchUpResponseTime records when is the latest response of each 
node's catch-up. There
@@ -270,32 +253,24 @@ public abstract class RaftMember implements 
RaftMemberMBean {
    * one slow node.
    */
   private ExecutorService serialToParallelPool;
-  /**
-   * a thread pool that is used to do commit log tasks asynchronous in 
heartbeat thread
-   */
+  /** a thread pool that is used to do commit log tasks asynchronous in 
heartbeat thread */
   private ExecutorService commitLogPool;
 
   /**
    * logDispatcher buff the logs orderly according to their log indexes and 
send them sequentially,
-   * which avoids the followers receiving out-of-order logs, forcing them to 
wait for previous
-   * logs.
+   * which avoids the followers receiving out-of-order logs, forcing them to 
wait for previous logs.
    */
   private volatile LogDispatcher logDispatcher;
 
-  /**
-   * If this node can not be the leader, this parameter will be set true.
-   */
+  /** If this node can not be the leader, this parameter will be set true. */
   private volatile boolean skipElection = false;
 
   /**
-   * localExecutor is used to directly execute plans like load configuration 
in the underlying
-   * IoTDB
+   * localExecutor is used to directly execute plans like load configuration 
in the underlying IoTDB
    */
   protected PlanExecutor localExecutor;
 
-  /**
-   * (logIndex, logTerm) -> append handler
-   */
+  /** (logIndex, logTerm) -> append handler */
   protected Map<Pair<Long, Long>, AppendNodeEntryHandler> sentLogHandlers =
       new ConcurrentHashMap<>();
 
@@ -305,8 +280,7 @@ public abstract class RaftMember implements RaftMemberMBean 
{
 
   private volatile LogAppender logAppender;
 
-  protected RaftMember() {
-  }
+  protected RaftMember() {}
 
   protected RaftMember(String name, ClientManager clientManager) {
     this.name = name;
@@ -368,7 +342,7 @@ public abstract class RaftMember implements RaftMemberMBean 
{
     if (logAppender == null) {
       synchronized (this) {
         if (logAppender == null) {
-          logAppender = APPENDER_FACTORY.create(this);
+          logAppender = appenderFactory.create(this);
         }
       }
     }
@@ -662,9 +636,7 @@ public abstract class RaftMember implements RaftMemberMBean 
{
     }
   }
 
-  /**
-   * Similar to appendEntry, while the incoming load is batch of logs instead 
of a single log.
-   */
+  /** Similar to appendEntry, while the incoming load is batch of logs instead 
of a single log. */
   public AppendEntryResult appendEntries(AppendEntriesRequest request)
       throws UnknownLogTypeException {
     logger.debug("{} received an AppendEntriesRequest", name);
@@ -695,9 +667,9 @@ public abstract class RaftMember implements RaftMemberMBean 
{
 
     
Timer.Statistic.RAFT_RECEIVER_LOG_PARSE.calOperationCostTimeFromStart(startTime);
 
-    response = getLogAppender()
-        .appendEntries(request.prevLogIndex, request.prevLogTerm, 
request.leaderCommit,
-            logs);
+    response =
+        getLogAppender()
+            .appendEntries(request.prevLogIndex, request.prevLogTerm, 
request.leaderCommit, logs);
     if (logger.isDebugEnabled()) {
       logger.debug(
           "{} AppendEntriesRequest of log size {} completed with result {}",
@@ -818,22 +790,16 @@ public abstract class RaftMember implements 
RaftMemberMBean {
     return lastCatchUpResponseTime;
   }
 
-  /**
-   * Sub-classes will add their own process of HeartBeatResponse in this 
method.
-   */
-  public void processValidHeartbeatResp(HeartBeatResponse response, Node 
receiver) {
-  }
+  /** Sub-classes will add their own process of HeartBeatResponse in this 
method. */
+  public void processValidHeartbeatResp(HeartBeatResponse response, Node 
receiver) {}
 
-  /**
-   * The actions performed when the node wins in an election (becoming a 
leader).
-   */
-  public void onElectionWins() {
-  }
+  /** The actions performed when the node wins in an election (becoming a 
leader). */
+  public void onElectionWins() {}
 
   /**
    * Update the followers' log by sending logs whose index >= 
followerLastMatchedLogIndex to the
-   * follower. If some of the required logs are removed, also send the 
snapshot. <br> notice that if
-   * a part of data is in the snapshot, then it is not in the logs.
+   * follower. If some of the required logs are removed, also send the 
snapshot. <br>
+   * notice that if a part of data is in the snapshot, then it is not in the 
logs.
    */
   public void catchUp(Node follower, long lastLogIdx) {
     // for one follower, there is at most one ongoing catch-up, so the same 
data will not be sent
@@ -927,9 +893,7 @@ public abstract class RaftMember implements RaftMemberMBean 
{
         "%s:%s=%s", "org.apache.iotdb.cluster.service", 
IoTDBConstant.JMX_TYPE, "Engine");
   }
 
-  /**
-   * call back after syncLeader
-   */
+  /** call back after syncLeader */
   public interface CheckConsistency {
 
     /**
@@ -938,7 +902,7 @@ public abstract class RaftMember implements RaftMemberMBean 
{
      * @param leaderCommitId leader commit id
      * @param localAppliedId local applied id
      * @throws CheckConsistencyException maybe throw 
CheckConsistencyException, which is defined in
-     *                                   implements.
+     *     implements.
      */
     void postCheckConsistency(long leaderCommitId, long localAppliedId)
         throws CheckConsistencyException;
@@ -947,7 +911,8 @@ public abstract class RaftMember implements RaftMemberMBean 
{
   public static class MidCheckConsistency implements CheckConsistency {
 
     /**
-     * if leaderCommitId - localAppliedId > MaxReadLogLag, will throw 
CHECK_MID_CONSISTENCY_EXCEPTION
+     * if leaderCommitId - localAppliedId > MaxReadLogLag, will throw
+     * CHECK_MID_CONSISTENCY_EXCEPTION
      *
      * @param leaderCommitId leader commit id
      * @param localAppliedId local applied id
@@ -959,7 +924,7 @@ public abstract class RaftMember implements RaftMemberMBean 
{
       if (leaderCommitId == Long.MAX_VALUE
           || leaderCommitId == Long.MIN_VALUE
           || leaderCommitId - localAppliedId
-          > ClusterDescriptor.getInstance().getConfig().getMaxReadLogLag()) {
+              > 
ClusterDescriptor.getInstance().getConfig().getMaxReadLogLag()) {
         throw CheckConsistencyException.CHECK_MID_CONSISTENCY_EXCEPTION;
       }
     }
@@ -992,7 +957,7 @@ public abstract class RaftMember implements RaftMemberMBean 
{
    * @param checkConsistency check after syncleader
    * @return true if the node has caught up, false otherwise
    * @throws CheckConsistencyException if leaderCommitId bigger than 
localAppliedId a threshold
-   *                                   value after timeout
+   *     value after timeout
    */
   public boolean syncLeader(CheckConsistency checkConsistency) throws 
CheckConsistencyException {
     if (character == NodeCharacter.LEADER) {
@@ -1011,9 +976,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
     return waitUntilCatchUp(checkConsistency);
   }
 
-  /**
-   * Wait until the leader of this node becomes known or time out.
-   */
+  /** Wait until the leader of this node becomes known or time out. */
   public void waitLeader() {
     long startTime = System.currentTimeMillis();
     while (leader.get() == null || 
ClusterConstant.EMPTY_NODE.equals(leader.get())) {
@@ -1040,7 +1003,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
    *
    * @return true if this node has caught up before timeout, false otherwise
    * @throws CheckConsistencyException if leaderCommitId bigger than 
localAppliedId a threshold
-   *                                   value after timeout
+   *     value after timeout
    */
   protected boolean waitUntilCatchUp(CheckConsistency checkConsistency)
       throws CheckConsistencyException {
@@ -1073,7 +1036,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
    * sync local applyId to leader commitId
    *
    * @param leaderCommitId leader commit id
-   * @param fastFail       if enable, when log differ too much, return false 
directly.
+   * @param fastFail if enable, when log differ too much, return false 
directly.
    * @return true if leaderCommitId <= localAppliedId
    */
   public boolean syncLocalApply(long leaderCommitId, boolean fastFail) {
@@ -1126,7 +1089,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
    * call this method. Will commit the log locally and send it to followers
    *
    * @return OK if over half of the followers accept the log or null if the 
leadership is lost
-   * during the appending
+   *     during the appending
    */
   public TSStatus processPlanLocally(PhysicalPlan plan) {
     if (USE_LOG_DISPATCHER) {
@@ -1234,7 +1197,8 @@ public abstract class RaftMember implements 
RaftMemberMBean {
           
Statistic.LOG_DISPATCHER_FROM_CREATE_TO_OK.calOperationCostTimeFromStart(
               log.getCreateTime());
           
Statistic.LOG_DISPATCHER_TOTAL.calOperationCostTimeFromStart(totalStartTime);
-          return StatusUtils.OK;
+          return StatusUtils.getStatus(TSStatusCode.WEAKLY_ACCEPTED)
+              .setMessage(log.getCurrLogIndex() + "-" + log.getCurrLogTerm());
         case OK:
           logger.debug(MSG_LOG_IS_ACCEPTED, name, log);
           startTime = 
Timer.Statistic.RAFT_SENDER_COMMIT_LOG.getOperationStartTime();
@@ -1243,7 +1207,9 @@ public abstract class RaftMember implements 
RaftMemberMBean {
           
Statistic.LOG_DISPATCHER_FROM_CREATE_TO_OK.calOperationCostTimeFromStart(
               log.getCreateTime());
           
Statistic.LOG_DISPATCHER_TOTAL.calOperationCostTimeFromStart(totalStartTime);
-          return StatusUtils.OK;
+          return StatusUtils.OK
+              .deepCopy()
+              .setMessage(log.getCurrLogIndex() + "-" + log.getCurrLogTerm());
         case TIME_OUT:
           logger.debug("{}: log {} timed out...", name, log);
           break;
@@ -1363,9 +1329,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
     return peerMap;
   }
 
-  /**
-   * @return true if there is a log whose index is "index" and term is "term", 
false otherwise
-   */
+  /** @return true if there is a log whose index is "index" and term is 
"term", false otherwise */
   public boolean matchLog(long index, long term) {
     boolean matched = logManager.matchTerm(term, index);
     logger.debug("Log {}-{} matched: {}", index, term, matched);
@@ -1384,18 +1348,15 @@ public abstract class RaftMember implements 
RaftMemberMBean {
     return syncLock;
   }
 
-  /**
-   * Sub-classes will add their own process of HeartBeatRequest in this method.
-   */
-  void processValidHeartbeatReq(HeartBeatRequest request, HeartBeatResponse 
response) {
-  }
+  /** Sub-classes will add their own process of HeartBeatRequest in this 
method. */
+  void processValidHeartbeatReq(HeartBeatRequest request, HeartBeatResponse 
response) {}
 
   /**
    * Verify the validity of an ElectionRequest, and make itself a follower of 
the elector if the
    * request is valid.
    *
    * @return Response.RESPONSE_AGREE if the elector is valid or the local term 
if the elector has a
-   * smaller term or Response.RESPONSE_LOG_MISMATCH if the elector has older 
logs.
+   *     smaller term or Response.RESPONSE_LOG_MISMATCH if the elector has 
older logs.
    */
   long checkElectorLogProgress(ElectionRequest electionRequest) {
 
@@ -1439,7 +1400,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
    * lastLogIndex is smaller than the voter's Otherwise accept the election.
    *
    * @return Response.RESPONSE_AGREE if the elector is valid or the local term 
if the elector has a
-   * smaller term or Response.RESPONSE_LOG_MISMATCH if the elector has older 
logs.
+   *     smaller term or Response.RESPONSE_LOG_MISMATCH if the elector has 
older logs.
    */
   long checkLogProgress(long lastLogIndex, long lastLogTerm) {
     long response;
@@ -1456,10 +1417,10 @@ public abstract class RaftMember implements 
RaftMemberMBean {
   /**
    * Forward a non-query plan to a node using the default client.
    *
-   * @param plan   a non-query plan
-   * @param node   cannot be the local node
+   * @param plan a non-query plan
+   * @param node cannot be the local node
    * @param header must be set for data group communication, set to null for 
meta group
-   *               communication
+   *     communication
    * @return a TSStatus indicating if the forwarding is successful.
    */
   public TSStatus forwardPlan(PhysicalPlan plan, Node node, RaftNode header) {
@@ -1490,7 +1451,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
   /**
    * Forward a non-query plan to "receiver" using "client".
    *
-   * @param plan   a non-query plan
+   * @param plan a non-query plan
    * @param header to determine which DataGroupMember of "receiver" will 
process the request.
    * @return a TSStatus indicating if the forwarding is successful.
    */
@@ -1572,7 +1533,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
    * Get an asynchronous thrift client of the given node.
    *
    * @return an asynchronous thrift client or null if the caller tries to 
connect the local node or
-   * the node cannot be reached.
+   *     the node cannot be reached.
    */
   public AsyncClient getAsyncClient(Node node) {
     try {
@@ -1680,6 +1641,14 @@ public abstract class RaftMember implements 
RaftMemberMBean {
     return logDispatcher;
   }
 
+  private boolean canBeWeaklyAccepted(Log log) {
+    if (!(log instanceof PhysicalPlanLog)) {
+      return false;
+    }
+    PhysicalPlanLog physicalPlanLog = (PhysicalPlanLog) log;
+    return physicalPlanLog.getPlan() instanceof InsertPlan;
+  }
+
   /**
    * wait until "voteCounter" counts down to zero, which means the quorum has 
received the log, or
    * one follower tells the node that it is no longer a valid leader, or a 
timeout is triggered.
@@ -1699,9 +1668,9 @@ public abstract class RaftMember implements 
RaftMemberMBean {
       long waitStart = System.currentTimeMillis();
       long alreadyWait = 0;
       while (stronglyAcceptedNodeNum < quorumSize
-          && (!ENABLE_WEAK_ACCEPTANCE
-          || (totalAccepted < allNodes.size() - 1)
-          || votingLogList.size() > config.getMaxNumOfLogsInMem())
+          && (!(ENABLE_WEAK_ACCEPTANCE && canBeWeaklyAccepted(log.getLog()))
+              || (totalAccepted < allNodes.size() - 1)
+              || votingLogList.size() > config.getMaxNumOfLogsInMem())
           && alreadyWait < ClusterConstant.getWriteOperationTimeoutMS()
           && !log.getStronglyAcceptedNodeIds().contains(Integer.MAX_VALUE)) {
         try {
@@ -1863,7 +1832,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
    * heartbeat timer.
    *
    * @param fromLeader true if the request is from a leader, false if the 
request is from an
-   *                   elector.
+   *     elector.
    */
   public void stepDown(long newTerm, boolean fromLeader) {
     synchronized (term) {
@@ -1895,9 +1864,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
     this.thisNode = thisNode;
   }
 
-  /**
-   * @return the header of the data raft group or null if this is in a meta 
group.
-   */
+  /** @return the header of the data raft group or null if this is in a meta 
group. */
   public RaftNode getHeader() {
     return null;
   }
@@ -2065,9 +2032,7 @@ public abstract class RaftMember implements 
RaftMemberMBean {
         log, node, leaderShipStale, newLeaderTerm, request, quorumSize, 
Collections.emptyList());
   }
 
-  /**
-   * Send "log" to "node".
-   */
+  /** Send "log" to "node". */
   public void sendLogToFollower(
       VotingLog log,
       Node node,
diff --git 
a/server/src/main/java/org/apache/iotdb/db/qp/physical/PhysicalPlan.java 
b/server/src/main/java/org/apache/iotdb/db/qp/physical/PhysicalPlan.java
index 33f1ca7..356c572 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/PhysicalPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/PhysicalPlan.java
@@ -103,6 +103,9 @@ public abstract class PhysicalPlan {
 
   // a bridge from a cluster raft log to a physical plan
   protected long index;
+  // if set, only the associated leader can execute the plan to guarantee 
serializability and other
+  // leaders should return a LEADER_CHANGED response
+  protected long targetedTerm = -1;
 
   private boolean debug;
 
@@ -564,4 +567,12 @@ public abstract class PhysicalPlan {
    * @throws QueryProcessException when the check fails
    */
   public void checkIntegrity() throws QueryProcessException {}
+
+  public long getTargetedTerm() {
+    return targetedTerm;
+  }
+
+  public void setTargetedTerm(long targetedTerm) {
+    this.targetedTerm = targetedTerm;
+  }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java 
b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
index 4516588..76fdb33 100644
--- a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
@@ -380,6 +380,8 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
       try {
         PhysicalPlan physicalPlan =
             processor.parseSQLToPhysicalPlan(statement, 
sessionManager.getZoneId(req.sessionId));
+        physicalPlan.setTargetedTerm(req.latestTerm);
+
         if (physicalPlan.isQuery() || physicalPlan.isSelectInto()) {
           throw new QueryInBatchStatementException(statement);
         }
@@ -480,6 +482,7 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
 
       PhysicalPlan physicalPlan =
           processor.parseSQLToPhysicalPlan(statement, 
sessionManager.getZoneId(req.getSessionId()));
+      physicalPlan.setTargetedTerm(req.latestTerm);
 
       return physicalPlan.isQuery()
           ? internalExecuteQueryStatement(
@@ -1112,6 +1115,7 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
     try {
       PhysicalPlan physicalPlan =
           processor.parseSQLToPhysicalPlan(req.statement, 
sessionManager.getZoneId(req.sessionId));
+      physicalPlan.setTargetedTerm(req.latestTerm);
       return physicalPlan.isQuery()
           ? RpcUtils.getTSExecuteStatementResp(
               TSStatusCode.EXECUTE_STATEMENT_ERROR, "Statement is a query 
statement.")
@@ -1155,7 +1159,8 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
     return status != null
         ? new TSExecuteStatementResp(status)
         : RpcUtils.getTSExecuteStatementResp(executeNonQueryPlan(plan))
-            .setQueryId(sessionManager.requestQueryId(false));
+            .setQueryId(sessionManager.requestQueryId(false))
+            .setOperationType(plan.getOperatorType().name());
   }
 
   protected void handleClientExit() {
@@ -1263,6 +1268,7 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
                     e, OperationType.INSERT_RECORDS, 
TSStatusCode.INTERNAL_SERVER_ERROR));
       }
     }
+    insertRowsPlan.setTargetedTerm(req.latestTerm);
     TSStatus tsStatus = executeNonQueryPlan(insertRowsPlan);
 
     return judgeFinalTsStatus(
@@ -1314,6 +1320,7 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
               req.getValuesList().toArray(new ByteBuffer[0]),
               req.isAligned);
       TSStatus status = checkAuthority(plan, req.getSessionId());
+      plan.setTargetedTerm(req.latestTerm);
       statusList.add(status != null ? status : executeNonQueryPlan(plan));
     } catch (IoTDBException e) {
       statusList.add(
@@ -1383,6 +1390,7 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
         allCheckSuccess = false;
       }
     }
+    insertRowsPlan.setTargetedTerm(req.latestTerm);
     TSStatus tsStatus = executeNonQueryPlan(insertRowsPlan);
 
     return judgeFinalTsStatus(
@@ -1470,6 +1478,7 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
               req.values,
               req.isAligned);
       TSStatus status = checkAuthority(plan, req.getSessionId());
+      plan.setTargetedTerm(req.latestTerm);
       return status != null ? status : executeNonQueryPlan(plan);
     } catch (IoTDBException e) {
       return onIoTDBException(e, OperationType.INSERT_RECORD, 
e.getErrorCode());
@@ -1501,6 +1510,7 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
       plan.setNeedInferType(true);
       plan.setAligned(req.isAligned);
       TSStatus status = checkAuthority(plan, req.getSessionId());
+      plan.setTargetedTerm(req.latestTerm);
       return status != null ? status : executeNonQueryPlan(plan);
     } catch (IoTDBException e) {
       return onIoTDBException(e, OperationType.INSERT_STRING_RECORD, 
e.getErrorCode());
@@ -1556,7 +1566,7 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
       insertTabletPlan.setDataTypes(req.types);
       insertTabletPlan.setAligned(req.isAligned);
       TSStatus status = checkAuthority(insertTabletPlan, req.getSessionId());
-
+      insertTabletPlan.setTargetedTerm(req.latestTerm);
       return status != null ? status : executeNonQueryPlan(insertTabletPlan);
     } catch (IoTDBException e) {
       return onIoTDBException(e, OperationType.INSERT_TABLET, 
e.getErrorCode());
@@ -1626,6 +1636,7 @@ public class TSServiceImpl extends BasicServiceProvider 
implements TSIService.If
     }
 
     insertMultiTabletPlan.setInsertTabletPlanList(insertTabletPlanList);
+    insertMultiTabletPlan.setTargetedTerm(req.latestTerm);
     return executeNonQueryPlan(insertMultiTabletPlan);
   }
 
diff --git a/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java 
b/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
index ebc1354..51ac057 100644
--- a/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
+++ b/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java
@@ -27,6 +27,7 @@ public enum TSStatusCode {
   STILL_EXECUTING_STATUS(201),
   INVALID_HANDLE_STATUS(202),
   INCOMPATIBLE_VERSION(203),
+  WEAKLY_ACCEPTED(204),
 
   NODE_DELETE_FAILED_ERROR(298),
   ALIAS_ALREADY_EXIST_ERROR(299),
@@ -101,6 +102,7 @@ public enum TSStatusCode {
   NO_CONNECTION(706),
   NEED_REDIRECTION(707),
   PARSE_LOG_ERROR(708),
+  LEADER_CHANGED(709),
 
   // configuration
   CONFIG_ERROR(800);
diff --git 
a/session/src/main/java/org/apache/iotdb/session/SessionConnection.java 
b/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
index de6350e..2c061c3 100644
--- a/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
+++ b/session/src/main/java/org/apache/iotdb/session/SessionConnection.java
@@ -24,6 +24,7 @@ import org.apache.iotdb.rpc.RedirectException;
 import org.apache.iotdb.rpc.RpcTransportFactory;
 import org.apache.iotdb.rpc.RpcUtils;
 import org.apache.iotdb.rpc.StatementExecutionException;
+import org.apache.iotdb.rpc.TSStatusCode;
 import org.apache.iotdb.service.rpc.thrift.EndPoint;
 import org.apache.iotdb.service.rpc.thrift.TSAppendSchemaTemplateReq;
 import org.apache.iotdb.service.rpc.thrift.TSCloseSessionReq;
@@ -83,6 +84,9 @@ public class SessionConnection {
   private List<EndPoint> endPointList = new ArrayList<>();
   private boolean enableRedirect = false;
 
+  private List<UnconfirmedRequest> unconfirmedRequests = new ArrayList<>();
+  private long latestTerm;
+
   // TestOnly
   public SessionConnection() {}
 
@@ -364,16 +368,26 @@ public class SessionConnection {
   protected void executeNonQueryStatement(String sql)
       throws IoTDBConnectionException, StatementExecutionException {
     TSExecuteStatementReq execReq = new TSExecuteStatementReq(sessionId, sql, 
statementId);
+    TSExecuteStatementResp execResp;
     try {
       execReq.setEnableRedirectQuery(enableRedirect);
-      TSExecuteStatementResp execResp = client.executeUpdateStatement(execReq);
-      RpcUtils.verifySuccess(execResp.getStatus());
+      execResp = client.executeUpdateStatement(execReq);
+      if (execResp.operationType != null && 
execResp.operationType.contains("INSERT")) {
+        verifyInsertionSuccess(execResp.getStatus(), execReq);
+      } else {
+        RpcUtils.verifySuccess(execResp.getStatus());
+      }
     } catch (TException e) {
       if (reconnect()) {
         try {
           execReq.setSessionId(sessionId);
           execReq.setStatementId(statementId);
-          
RpcUtils.verifySuccess(client.executeUpdateStatement(execReq).status);
+          execResp = client.executeUpdateStatement(execReq);
+          if (execResp.operationType != null && 
execResp.operationType.contains("INSERT")) {
+            verifyInsertionSuccess(execResp.getStatus(), execReq);
+          } else {
+            RpcUtils.verifySuccess(execResp.getStatus());
+          }
         } catch (TException tException) {
           throw new IoTDBConnectionException(tException);
         }
@@ -462,13 +476,14 @@ public class SessionConnection {
   protected void insertRecord(TSInsertRecordReq request)
       throws IoTDBConnectionException, StatementExecutionException, 
RedirectException {
     request.setSessionId(sessionId);
+    request.setLatestTerm(latestTerm);
     try {
-      RpcUtils.verifySuccessWithRedirection(client.insertRecord(request));
+      verifyInsertionSuccessWithRedirection(client.insertRecord(request), 
request);
     } catch (TException e) {
       if (reconnect()) {
         try {
           request.setSessionId(sessionId);
-          RpcUtils.verifySuccess(client.insertRecord(request));
+          verifyInsertionSuccess(client.insertRecord(request), request);
         } catch (TException tException) {
           throw new IoTDBConnectionException(tException);
         }
@@ -481,13 +496,14 @@ public class SessionConnection {
   protected void insertRecord(TSInsertStringRecordReq request)
       throws IoTDBConnectionException, StatementExecutionException, 
RedirectException {
     request.setSessionId(sessionId);
+    request.setLatestTerm(latestTerm);
     try {
-      
RpcUtils.verifySuccessWithRedirection(client.insertStringRecord(request));
+      
verifyInsertionSuccessWithRedirection(client.insertStringRecord(request), 
request);
     } catch (TException e) {
       if (reconnect()) {
         try {
           request.setSessionId(sessionId);
-          RpcUtils.verifySuccess(client.insertStringRecord(request));
+          verifyInsertionSuccess(client.insertStringRecord(request), request);
         } catch (TException tException) {
           throw new IoTDBConnectionException(tException);
         }
@@ -500,14 +516,15 @@ public class SessionConnection {
   protected void insertRecords(TSInsertRecordsReq request)
       throws IoTDBConnectionException, StatementExecutionException, 
RedirectException {
     request.setSessionId(sessionId);
+    request.setLatestTerm(latestTerm);
     try {
-      RpcUtils.verifySuccessWithRedirectionForMultiDevices(
-          client.insertRecords(request), request.getPrefixPaths());
+      verifyInsertionWithRedirectionForMultiDevices(
+          client.insertRecords(request), request.getPrefixPaths(), request);
     } catch (TException e) {
       if (reconnect()) {
         try {
           request.setSessionId(sessionId);
-          RpcUtils.verifySuccess(client.insertRecords(request));
+          verifyInsertionSuccess(client.insertRecords(request), request);
         } catch (TException tException) {
           throw new IoTDBConnectionException(tException);
         }
@@ -520,14 +537,15 @@ public class SessionConnection {
   protected void insertRecords(TSInsertStringRecordsReq request)
       throws IoTDBConnectionException, StatementExecutionException, 
RedirectException {
     request.setSessionId(sessionId);
+    request.setLatestTerm(latestTerm);
     try {
-      RpcUtils.verifySuccessWithRedirectionForMultiDevices(
-          client.insertStringRecords(request), request.getPrefixPaths());
+      verifyInsertionWithRedirectionForMultiDevices(
+          client.insertStringRecords(request), request.getPrefixPaths(), 
request);
     } catch (TException e) {
       if (reconnect()) {
         try {
           request.setSessionId(sessionId);
-          RpcUtils.verifySuccess(client.insertStringRecords(request));
+          verifyInsertionSuccess(client.insertStringRecords(request), request);
         } catch (TException tException) {
           throw new IoTDBConnectionException(tException);
         }
@@ -540,13 +558,14 @@ public class SessionConnection {
   protected void insertRecordsOfOneDevice(TSInsertRecordsOfOneDeviceReq 
request)
       throws IoTDBConnectionException, StatementExecutionException, 
RedirectException {
     request.setSessionId(sessionId);
+    request.setLatestTerm(latestTerm);
     try {
-      
RpcUtils.verifySuccessWithRedirection(client.insertRecordsOfOneDevice(request));
+      
verifyInsertionSuccessWithRedirection(client.insertRecordsOfOneDevice(request), 
request);
     } catch (TException e) {
       if (reconnect()) {
         try {
           request.setSessionId(sessionId);
-          RpcUtils.verifySuccess(client.insertRecordsOfOneDevice(request));
+          verifyInsertionSuccess(client.insertRecordsOfOneDevice(request), 
request);
         } catch (TException tException) {
           throw new IoTDBConnectionException(tException);
         }
@@ -559,13 +578,14 @@ public class SessionConnection {
   protected void insertTablet(TSInsertTabletReq request)
       throws IoTDBConnectionException, StatementExecutionException, 
RedirectException {
     request.setSessionId(sessionId);
+    request.setLatestTerm(latestTerm);
     try {
-      RpcUtils.verifySuccessWithRedirection(client.insertTablet(request));
+      verifyInsertionSuccessWithRedirection(client.insertTablet(request), 
request);
     } catch (TException e) {
       if (reconnect()) {
         try {
           request.setSessionId(sessionId);
-          RpcUtils.verifySuccess(client.insertTablet(request));
+          verifyInsertionSuccess(client.insertTablet(request), request);
         } catch (TException tException) {
           throw new IoTDBConnectionException(tException);
         }
@@ -578,14 +598,15 @@ public class SessionConnection {
   protected void insertTablets(TSInsertTabletsReq request)
       throws IoTDBConnectionException, StatementExecutionException, 
RedirectException {
     request.setSessionId(sessionId);
+    request.setLatestTerm(latestTerm);
     try {
-      RpcUtils.verifySuccessWithRedirectionForMultiDevices(
-          client.insertTablets(request), request.getPrefixPaths());
+      verifyInsertionWithRedirectionForMultiDevices(
+          client.insertTablets(request), request.getPrefixPaths(), request);
     } catch (TException e) {
       if (reconnect()) {
         try {
           request.setSessionId(sessionId);
-          RpcUtils.verifySuccess(client.insertTablets(request));
+          verifyInsertionSuccess(client.insertTablets(request), request);
         } catch (TException tException) {
           throw new IoTDBConnectionException(tException);
         }
@@ -913,8 +934,128 @@ public class SessionConnection {
     this.endPoint = endPoint;
   }
 
+  private void verifyInsertionSuccess(TSStatus status, Object request)
+      throws StatementExecutionException, IoTDBConnectionException {
+    if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+      onStronglyAccepted(status.getMessage());
+    } else if (status.getCode() == 
TSStatusCode.WEAKLY_ACCEPTED.getStatusCode()) {
+      onWeaklyAccepted(status.getMessage(), request);
+    } else if (status.getCode() == 
TSStatusCode.LEADER_CHANGED.getStatusCode()) {
+      onLeaderChange(status.getMessage(), request);
+    } else {
+      RpcUtils.verifySuccess(status);
+    }
+  }
+
+  private void verifyInsertionSuccessWithRedirection(TSStatus status, Object 
request)
+      throws StatementExecutionException, RedirectException, 
IoTDBConnectionException {
+    if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+      onStronglyAccepted(status.getMessage());
+    } else if (status.getCode() == 
TSStatusCode.WEAKLY_ACCEPTED.getStatusCode()) {
+      onWeaklyAccepted(status.getMessage(), request);
+    } else if (status.getCode() == 
TSStatusCode.LEADER_CHANGED.getStatusCode()) {
+      onLeaderChange(status.getMessage(), request);
+    } else {
+      RpcUtils.verifySuccessWithRedirection(status);
+    }
+  }
+
+  private void verifyInsertionWithRedirectionForMultiDevices(
+      TSStatus status, List<String> devices, Object request)
+      throws StatementExecutionException, RedirectException, 
IoTDBConnectionException {
+    if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+      onStronglyAccepted(status.getMessage());
+    } else if (status.getCode() == 
TSStatusCode.WEAKLY_ACCEPTED.getStatusCode()) {
+      onWeaklyAccepted(status.getMessage(), request);
+    } else if (status.getCode() == 
TSStatusCode.LEADER_CHANGED.getStatusCode()) {
+      onLeaderChange(status.getMessage(), request);
+    } else {
+      RpcUtils.verifySuccessWithRedirectionForMultiDevices(status, devices);
+    }
+  }
+
+  private void onStronglyAccepted(String message) {
+    if (message == null) {
+      return;
+    }
+    try {
+      String[] split = message.split("-");
+      long index = Long.parseLong(split[0]);
+      long term = Long.parseLong(split[1]);
+      if (!unconfirmedRequests.isEmpty() && unconfirmedRequests.get(0).term == 
term) {
+        unconfirmedRequests.removeIf(r -> r.index < index);
+      }
+      latestTerm = term;
+    } catch (IndexOutOfBoundsException | NumberFormatException e) {
+      // ignore
+    }
+  }
+
+  private void onWeaklyAccepted(String message, Object request) {
+    if (message == null) {
+      return;
+    }
+    try {
+      String[] split = message.split("-");
+      long index = Long.parseLong(split[0]);
+      long term = Long.parseLong(split[1]);
+      UnconfirmedRequest unconfirmedRequest = new UnconfirmedRequest();
+      unconfirmedRequest.index = index;
+      unconfirmedRequest.term = term;
+      unconfirmedRequest.request = request;
+      unconfirmedRequests.add(unconfirmedRequest);
+    } catch (IndexOutOfBoundsException | NumberFormatException e) {
+      // ignore
+    }
+  }
+
+  private void onLeaderChange(String message, Object request)
+      throws StatementExecutionException, IoTDBConnectionException {
+    if (message == null) {
+      return;
+    }
+    try {
+      latestTerm = Long.parseLong(message);
+      List<UnconfirmedRequest> tempUnconfirmedRequests = new 
ArrayList<>(unconfirmedRequests);
+      unconfirmedRequests.clear();
+      for (UnconfirmedRequest tempUnconfirmedRequest : 
tempUnconfirmedRequests) {
+        retryInsertion(tempUnconfirmedRequest.request);
+      }
+      retryInsertion(request);
+    } catch (IndexOutOfBoundsException | NumberFormatException | 
RedirectException e) {
+      // ignore
+    }
+  }
+
+  private void retryInsertion(Object request)
+      throws StatementExecutionException, IoTDBConnectionException, 
RedirectException {
+    if (request instanceof TSInsertRecordReq) {
+      insertRecord(((TSInsertRecordReq) request));
+    } else if (request instanceof TSInsertStringRecordReq) {
+      insertRecord(((TSInsertStringRecordReq) request));
+    } else if (request instanceof TSInsertRecordsReq) {
+      insertRecords(((TSInsertRecordsReq) request));
+    } else if (request instanceof TSInsertStringRecordsReq) {
+      insertRecords(((TSInsertStringRecordsReq) request));
+    } else if (request instanceof TSInsertRecordsOfOneDeviceReq) {
+      insertRecordsOfOneDevice(((TSInsertRecordsOfOneDeviceReq) request));
+    } else if (request instanceof TSInsertTabletReq) {
+      insertTablet(((TSInsertTabletReq) request));
+    } else if (request instanceof TSInsertTabletsReq) {
+      insertTablets(((TSInsertTabletsReq) request));
+    } else {
+      logger.error("Unknown request type during retry: {}", request);
+    }
+  }
+
   @Override
   public String toString() {
     return "SessionConnection{" + " endPoint=" + endPoint + "}";
   }
+
+  static class UnconfirmedRequest {
+    private long index;
+    private long term;
+    private Object request;
+  }
 }
diff --git a/thrift/src/main/thrift/rpc.thrift 
b/thrift/src/main/thrift/rpc.thrift
index a981a8c..534cd17 100644
--- a/thrift/src/main/thrift/rpc.thrift
+++ b/thrift/src/main/thrift/rpc.thrift
@@ -138,6 +138,8 @@ struct TSExecuteStatementReq {
   6: optional bool enableRedirectQuery;
 
   7: optional bool jdbcQuery;
+
+  8: optional i64 latestTerm
 }
 
 struct TSExecuteBatchStatementReq{
@@ -146,6 +148,8 @@ struct TSExecuteBatchStatementReq{
 
   // The statements to be executed (DML, DDL, SET, etc)
   2: required list<string> statements
+
+  3: optional i64 latestTerm
 }
 
 struct TSGetOperationStatusReq {
@@ -218,6 +222,7 @@ struct TSInsertRecordReq {
   4: required binary values
   5: required i64 timestamp
   6: optional bool isAligned
+  7: optional i64 latestTerm
 }
 
 struct TSInsertStringRecordReq {
@@ -227,6 +232,7 @@ struct TSInsertStringRecordReq {
   4: required list<string> values
   5: required i64 timestamp
   6: optional bool isAligned
+  7: optional i64 latestTerm
 }
 
 struct TSInsertTabletReq {
@@ -238,6 +244,7 @@ struct TSInsertTabletReq {
   6: required list<i32> types
   7: required i32 size
   8: optional bool isAligned
+  9: optional i64 latestTerm
 }
 
 struct TSInsertTabletsReq {
@@ -249,6 +256,7 @@ struct TSInsertTabletsReq {
   6: required list<list<i32>> typesList
   7: required list<i32> sizeList
   8: optional bool isAligned
+  9: optional i64 latestTerm
 }
 
 struct TSInsertRecordsReq {
@@ -258,6 +266,7 @@ struct TSInsertRecordsReq {
   4: required list<binary> valuesList
   5: required list<i64> timestamps
   6: optional bool isAligned
+  7: optional i64 latestTerm
 }
 
 struct TSInsertRecordsOfOneDeviceReq {
@@ -267,6 +276,7 @@ struct TSInsertRecordsOfOneDeviceReq {
     4: required list<binary> valuesList
     5: required list<i64> timestamps
     6: optional bool isAligned
+    7: optional i64 latestTerm
 }
 
 struct TSInsertStringRecordsReq {
@@ -276,6 +286,7 @@ struct TSInsertStringRecordsReq {
   4: required list<list<string>> valuesList
   5: required list<i64> timestamps
   6: optional bool isAligned
+  7: optional i64 latestTerm
 }
 
 struct TSDeleteDataReq {

Reply via email to