Skip to content

RATIS-2603. Add DataStreamApi.Resolver for group-independent read-only requests#1516

Merged
szetszwo merged 8 commits into
apache:masterfrom
peterxcli:RATIS-2603
Jul 14, 2026
Merged

RATIS-2603. Add DataStreamApi.Resolver for group-independent read-only requests#1516
szetszwo merged 8 commits into
apache:masterfrom
peterxcli:RATIS-2603

Conversation

@peterxcli

@peterxcli peterxcli commented Jul 13, 2026

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

RATIS-2603
Users such as Ozone may archive part of their data and mark it immutable. In Ozone, closed containers use a synthetic read pipeline whose ID does not correspond to a live Ratis group, causing DataStream reads to fail with GroupMismatchException.

Introducing a new DataStreamReadResolver, which should provide an optional path to bypass group lookup and let the application define how such reads are handled. If the resolver declines the request, the existing Ratis read path remains unchanged.

How could Users Use this?

for example, in ozone, we register our resolver to DataStreamReadResolver in XceiverServerRatis#newXceiverServerRatis when OzoneContainer starts.

public static XceiverServerRatis newXceiverServerRatis(...){
  // ...
  ClosedContainerReadResolver resolver = new ClosedContainerReadResolver(dispatcher, 
    containerController, datanodeDetails);
  DataStreamReadResolver.set(parameters, resolver);
  // ...
}

and the resolver implmentaion should be like:

/** Resolves group-independent reads against immutable local containers. */
final class ClosedContainerReadResolver implements DataStreamReadResolver {
  private final ContainerDispatcher dispatcher;
  private final ContainerController containerController;
  private final String datanodeUuid;

  ClosedContainerReadResolver(ContainerDispatcher dispatcher,
      ContainerController containerController, DatanodeDetails datanode) {
    this.dispatcher = dispatcher;
    this.containerController = containerController;
    this.datanodeUuid = datanode.getUuidString();
  }

  @Override
  public StateMachine.DataApi resolve(RaftClientRequest request)
      throws IOException {
    final ContainerCommandRequestProto requestProto = ContainerCommandRequestMessage.toProto(
        request.getMessage().getContent(), request.getRaftGroupId());
    if (requestProto.getCmdType() != Type.ReadBlock
        || requestProto.hasDatanodeUuid()
            && !datanodeUuid.equals(requestProto.getDatanodeUuid())) {
      return null;
    }

    final Container<?> container =
        containerController.getContainer(requestProto.getContainerID());
    if (container == null || container.getContainerState() != CLOSED) {
      return null;
    }

    return new StateMachine.DataApi() {
      @Override
      public void query(Message ignored, WritableByteChannel stream) {
        try {
          ContainerStateMachine.streamReadBlock(
              dispatcher, requestProto, stream);
        } catch (IOException e) {
          throw new CompletionException(e);
        }
      }
    };
  }
}

What is the link to the Apache JIRA

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

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 Jul 13, 2026

Copy link
Copy Markdown
Member Author

apache/ozone#10743 is the patch that use the resolver.

cc @szetszwo @amaliujia, please could you take a look at them whenever you have time, thanks!

}

/** Get the resolver from the given server parameters, if configured. */
static DataStreamReadResolver get(Parameters parameters) {

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.

Nit: you can add @Nullable notation to Parameters

try {
dataApi = readResolver.resolve(request);
} catch (Throwable t) {
replyDataStreamException(server, t, request, requestBuf, ctx);

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.

Why we do not choose to fall back to division when see exceptions from the resolver>

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.

for example, the container is closed, it doesn't have the division anymore, if the resolve process thrown exception, then the fallback to division would just get the the GroupMismatchException

Signed-off-by: peterxcli <peterxcli@gmail.com>
Signed-off-by: peterxcli <peterxcli@gmail.com>
@peterxcli peterxcli requested a review from amaliujia July 14, 2026 05:22

@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 a lot for working on this. It is a really great idea!

Let's do a refactoring so that the new API is not in StateMachine; filed RATIS-2604. We could also make it similar to the FileChannel.transferTo(..) method. I have tried it a little bit; see https://issues.apache.org/jira/secure/attachment/13083178/1516_review.patch

/** Server side data stream API. */
public interface DataStreamApi { // TODO: add this RATIS-2604
  /**
   * Similar to {@link java.nio.channels.FileChannel#transferTo(long, long, WritableByteChannel)}
   * except that the parameter of this method is request message
   * but not position, count in FileChannel.
   *
   * @param request the request message
   * @param stream the output stream to send the results
   * @return the number of bytes transferred
   */
  long transferTo(Message request, WritableByteChannel stream) throws IOException;

  /** For resolving {@link DataStreamApi}. */
  @FunctionalInterface
  interface Resolver { // TODO: add this in RATIS-2603
    /** @return the data API handling this request, or null if the API is not found. */
    DataStreamApi resolve(RaftClientRequest request);
  }
}

Signed-off-by: peterxcli <peterxcli@gmail.com>
Signed-off-by: peterxcli <peterxcli@gmail.com>
Signed-off-by: peterxcli <peterxcli@gmail.com>
Signed-off-by: peterxcli <peterxcli@gmail.com>
Signed-off-by: peterxcli <peterxcli@gmail.com>
@peterxcli peterxcli requested a review from szetszwo July 14, 2026 11:26

@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 changed the title RATIS-2603. Add DataStreamReadResolver for group-independent read-only requests RATIS-2603. Add DataStreamApi.Resolver for group-independent read-only requests Jul 14, 2026
@szetszwo szetszwo merged commit 27cdb5d into apache:master Jul 14, 2026
31 of 32 checks passed
@peterxcli peterxcli deleted the RATIS-2603 branch July 15, 2026 04:34
@peterxcli

Copy link
Copy Markdown
Member Author

Thanks @szetszwo for the review, refactoring suggestion and renaming title! @amaliujia for the review!

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