Skip to content

RATIS-2559. Add linearizable check for streaming read request#1490

Merged
szetszwo merged 6 commits into
apache:masterfrom
peterxcli:RATIS-2559
Jul 6, 2026
Merged

RATIS-2559. Add linearizable check for streaming read request#1490
szetszwo merged 6 commits into
apache:masterfrom
peterxcli:RATIS-2559

Conversation

@peterxcli

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

  • Refactored ReadStreamManagement to perform an asynchronous linearizability check using a "dummy read" request before invoking the state machine for read-only queries. The state machine query is only performed if the check succeeds, and an appropriate reply is sent if it fails.
  • Updated RaftServerImpl to recognize and handle dummy read requests by returning a success reply immediately, avoiding unnecessary state machine queries.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/RATIS-2559

How was this patch tested?

(Please explain how this patch was tested. Ex: unit tests, manual tests)
(If this patch involves UI changes, please attach a screen-shot; otherwise, remove this)

Signed-off-by: peterxcli <peterxcli@gmail.com>
@peterxcli

peterxcli commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

cc @szetszwo @amaliujia

if (reply != null) {
return reply;
}
return isDummyRead(request) ? CompletableFuture.completedFuture(newSuccessReply(request))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to learn the context, why do we need this dummy read in Ratis streaming read while we do not have it for Ratis streaming write?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is to run the linearizable check; see #1469 (comment)

@szetszwo szetszwo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterxcli , thanks for working on this!

Please see the comments inlined and also https://issues.apache.org/jira/secure/attachment/13082966/1490_review.patch

Comment on lines +1147 to +1149
private static boolean isDummyRead(RaftClientRequest request) {
return request.getMessage() != null && OrderedAsync.DUMMY.getContent().equals(request.getMessage().getContent());
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot use DUMMY since user applications can set any message. They may already have used it in their messages. We probably need to add a flag to the proto:

+++ b/ratis-proto/src/main/proto/Raft.proto
@@ -302,6 +302,7 @@ message ForwardRequestTypeProto {
 message ReadRequestTypeProto {
   bool preferNonLinearizable = 1;
   bool readAfterWriteConsistent = 2;
+  bool dummy = 3;
 }

Comment on lines +1119 to +1120
return isDummyRead(request) ? CompletableFuture.completedFuture(newSuccessReply(request))
: queryStateMachine(request);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a DUMMY_SUCCESS_REPLY and move it to queryStateMachine:

  static final CompletableFuture<RaftClientReply> DUMMY_SUCCESS_REPLY
      = CompletableFuture.completedFuture(RaftClientReply.newBuilder().setSuccess().build());
  CompletableFuture<RaftClientReply> queryStateMachine(RaftClientRequest request) {
    if (request.getType().getRead().getDummy()) {
      return DUMMY_SUCCESS_REPLY;
    }
    return processQueryFuture(stateMachine.query(request.getMessage()), request);
  }

Comment on lines +193 to +197
final RaftClientReply terminalReply = toReadStreamReply(request, reply);
if (!reply.isSuccess()) {
ctx.writeAndFlush(newDataStreamReplyByteBuffer(requestBuf, terminalReply));
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since server returns a DUMMY_SUCCESS_REPLY, we could build the failed reply directly:

      if (!readCheckReply.isSuccess()) {
        final RaftClientReply reply = RaftClientReply.newBuilder()
            .setRequest(request)
            .setSuccess(false)
            .build();
        ctx.writeAndFlush(newDataStreamReplyByteBuffer(requestBuf, reply));
        return;
      }

      final ReadStream stream = new ReadStream(request, requestBuf.getStreamId(), ctx);

Comment on lines +210 to +226
final RaftClientRequest.Builder builder = RaftClientRequest.newBuilder()
.setClientId(request.getClientId())
.setGroupId(request.getRaftGroupId())
.setCallId(request.getCallId())
.setMessage(OrderedAsync.DUMMY)
.setType(request.getType())
.setRepliedCallIds(request.getRepliedCallIds())
.setSlidingWindowEntry(request.getSlidingWindowEntry())
.setRoutingTable(request.getRoutingTable())
.setTimeoutMs(request.getTimeoutMs())
.setSpanContext(request.getSpanContext());
if (request.isToLeader()) {
builder.setLeaderId(request.getServerId());
} else {
builder.setServerId(request.getServerId());
}
return builder.build();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a set(RaftClientRequest request) to Builder to copy everything.

Co-authored-by: szetszwo <szetszwo@apache.org>
Signed-off-by: peterxcli <peterxcli@gmail.com>
@szetszwo

szetszwo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@peterxcli , thanks for the update. There is a build failures. Could you take a look?

peterxcli added 3 commits July 4, 2026 18:37
Signed-off-by: peterxcli <peterxcli@gmail.com>
Signed-off-by: peterxcli <peterxcli@gmail.com>
Comment on lines -171 to -173
static final CompletableFuture<RaftClientReply> DUMMY_SUCCESS_REPLY
= CompletableFuture.completedFuture(RaftClientReply.newBuilder().setSuccess().build());

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This somehow leads the test failure.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines -171 to -172
static final CompletableFuture<RaftClientReply> DUMMY_SUCCESS_REPLY
= CompletableFuture.completedFuture(RaftClientReply.newBuilder().setSuccess().build());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to build the dummySuccessReply by setting the required fields. It seems working.

@@ -260,6 +261,8 @@ class RaftServerImpl implements RaftServer.Division,
   private final ExecutorService clientExecutor;
   private final ThreadGroup threadGroup;
 
+  private final CompletableFuture<RaftClientReply> dummySuccessReply;
+
   RaftServerImpl(RaftGroup group, StateMachine stateMachine, RaftServerProxy proxy, RaftStorage.StartupOption option)
       throws IOException {
     final RaftPeerId id = proxy.getId();
@@ -303,6 +306,13 @@ class RaftServerImpl implements RaftServer.Division,
         RaftServerConfigKeys.ThreadPool.clientSize(properties),
         id + "-client");
     this.threadGroup = new ThreadGroup(proxy.getThreadGroup(), getMemberId().toString());
+
+    this.dummySuccessReply = CompletableFuture.completedFuture(RaftClientReply.newBuilder()
+        .setClientId(ClientId.emptyClientId())
+        .setServerId(id)
+        .setGroupId(group.getGroupId())
+        .setSuccess()
+        .build());
   }
@@ -1183,6 +1199,9 @@ class RaftServerImpl implements RaftServer.Division,
   }
 
   CompletableFuture<RaftClientReply> queryStateMachine(RaftClientRequest request) {
+    if (request.getType().getRead().getDummy()) {
+      return dummySuccessReply;
+    }
     return processQueryFuture(stateMachine.query(request.getMessage()), request);
   }

Signed-off-by: peterxcli <peterxcli@gmail.com>

@szetszwo szetszwo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 the change looks good.

@szetszwo szetszwo merged commit 11e9e1f into apache:master Jul 6, 2026
18 checks passed
@peterxcli peterxcli deleted the RATIS-2559 branch July 6, 2026 09:24
@peterxcli

Copy link
Copy Markdown
Member Author

@szetszwo thanks for the review and the review patch! let me start to work on the integration with ozone

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants