@Override
  public DataSet read(ConsensusGroupId groupId, IConsensusRequest request)
      throws ConsensusException {
    RaftGroupId raftGroupId = Utils.fromConsensusGroupIdToRaftGroupId(groupId);
    RaftGroup group = getGroupInfo(raftGroupId);
    if (group == null || !group.getPeers().contains(myself)) {
      throw new ConsensusGroupNotExistException(groupId);
    }

    // perform linearizable read under following two conditions:
    // 1. Read.Option is linearizable
    // 2. First probing read when Read.Option is default
    final boolean isLinearizableRead =
        readOption == RatisConfig.Read.Option.LINEARIZABLE
            || !canServeStaleRead.computeIfAbsent(groupId, id -> new AtomicBoolean(false)).get();

    RaftClientReply reply;
    try {
      reply = doRead(raftGroupId, request, isLinearizableRead);
      // allow stale read if current linearizable read returns successfully
      if (canServeStaleRead != null && isLinearizableRead) {
        canServeStaleRead.get(groupId).set(true);
      }
    } catch (ReadException | ReadIndexException | NotLeaderException e) {
      if (isLinearizableRead) {
        // linearizable read failed. the RaftServer is recovering from Raft Log and cannot serve
        // read requests.
        throw new RatisReadUnavailableException(e);
      } else {
        throw new RatisRequestFailedException(e);
      }
    } catch (GroupMismatchException e) {
      throw new ConsensusGroupNotExistException(groupId);
    } catch (IllegalStateException e) {
      if (e.getMessage() != null && e.getMessage().contains("ServerNotReadyException")) {
        ServerNotReadyException serverNotReadyException =
            new ServerNotReadyException(e.getMessage());
        throw new RatisReadUnavailableException(serverNotReadyException);
      } else {
        throw new RatisRequestFailedException(e);
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RatisReadUnavailableException(e);
    } catch (Exception e) {
      throw new RatisRequestFailedException(e);
    }
    Message ret = reply.getMessage();
    ResponseMessage readResponseMessage = (ResponseMessage) ret;
    return (DataSet) readResponseMessage.getContentHolder();
  }
Read directly from LOCAL COPY notice, although we do some optimizations to try to ensure
linearizable (such as enforcing linearizable reads when the leader transfers), linearizable can
be violated in some extreme cases.
iotdb-core/consensus/src/main/java/org/apache/iotdb/consensus/ratis/RatisConsensus.java