java-driver icon indicating copy to clipboard operation
java-driver copied to clipboard

Add ExecutionInfo to RequestTracker methods

Open ajweave opened this issue 2 years ago • 15 comments

RequestTracker implementations may want to use the ExecutionInfo object e.g logging request/response size, etc.

ajweave avatar Jun 05 '23 20:06 ajweave

Thanks for the PR @ajweave!

Have you signed the Contributor License Agreement for contributions to DataStax open source projects? If not you can find it at https://cla.datastax.com/. Thanks!

absurdfarce avatar Jun 06 '23 20:06 absurdfarce

@ajweave Just so I have this straight in my own head, here's how I understand the relationship between this PR and the ask on JAVA-3046. This PR clearly extends the callbacks for request success and failure to include the extra ExecutionInfo object while JAVA-3046 asks for new callbacks all together for the start of a request. Do I have the contours basically right there?

absurdfarce avatar Jun 06 '23 20:06 absurdfarce

@absurdfarce the current behavior of RequestTracker providing success/failure after the fact is sufficient for my needs. The only additional thing I require is the ExecutionInfo. Making RequestTracker support finer-grained lifecycle events is ok with me as long as I can get my hands on ExecutionInfo in onSuccess.

ajweave avatar Jun 06 '23 21:06 ajweave

@absurdfarce I've signed the CLA.

ajweave avatar Jun 07 '23 14:06 ajweave

Now that the donation to Apache is complete, I'd like to move forward with this change. @hhughes , @absurdfarce , please take a look.

ajweave avatar Nov 08 '23 16:11 ajweave

@hhughes , @absurdfarce , gentle reminder to review these changes.

ajweave avatar Dec 04 '23 14:12 ajweave

+1 , this would be a great addition , allowing more granular access to execution metrics.

vanditsramblings avatar Mar 13 '24 17:03 vanditsramblings

Ping @SiyaoIsHiding for (hopefully) another review

absurdfarce avatar Apr 30 '24 21:04 absurdfarce

May you please merge apache/4.x to your branch?

SiyaoIsHiding avatar May 08 '24 22:05 SiyaoIsHiding

Sorry for the delay. I will get this done tomorrow. I appreciate the attention here.

ajweave avatar May 08 '24 23:05 ajweave

Good work @ajweave, sorry to keep you waiting. Two things to consider from my end. Feel free to disagree since both are more of coding style category.

In request handlers (e.g. CqlRequestHandler), we very frequently do two things with same arguments:

trackNodeError(node, error, NANOTIME_NOT_MEASURED_YET);
setFinalError(statement, error, node, execution);

Both methods create their own instance of DefaultExecutionInfo internally. Shall we create the ExecutionInfo outside and pass it as parameter? Some of the other parameters like statement or node, could be taken from ExecutionInfo and not passed individually. When we use NoopRequestTracker, we do not need to create DefaultExecutionInfo inside trackNodeError, but still it would be needed in setFinalError, so we shall not degrade performance in this case.

All of this can be done, because we decide to construct ExecutionInfo for all exceptional executions, not only once resulting in DriverException. Anyway, most of the time we would see DriverException being raised.

DefaultExecutionInfo has few optional parameters, but from what I see, we use the constructor in different contexts. What do you think about making this constructor private and expose create(...) methods to indicate what type of DefaultExecutionInfo we are going to pass? Also, would move pagingState calculation inside (currently code copied across different request handlers).

  /** Use when driver failed to send the request to the server, or we did not receive any response. */
  public static DefaultExecutionInfo create(Request request, Node coordinator, int speculativeExecutionCount,
                                            int successfulExecutionIndex, List<Map.Entry<Node, Throwable>> errors,
                                            DefaultSession session, InternalDriverContext context,
                                            DriverExecutionProfile executionProfile) {
    return create(request, coordinator, speculativeExecutionCount, successfulExecutionIndex, errors,
            null, null, true, session, context, executionProfile);
  }

  /** Use when driver received the response, but operation result remains unknown. */
  public static DefaultExecutionInfo create(Request request, Node coordinator, int speculativeExecutionCount,
                                            int successfulExecutionIndex, List<Map.Entry<Node, Throwable>> errors,
                                            Frame frame, DefaultSession session, InternalDriverContext context,
                                            DriverExecutionProfile executionProfile) {
    return create(request, coordinator, speculativeExecutionCount, successfulExecutionIndex, errors,
            null, frame, true, session, context, executionProfile);
  }

  public static DefaultExecutionInfo create(Request request, Node coordinator, int speculativeExecutionCount,
                                            int successfulExecutionIndex, List<Map.Entry<Node, Throwable>> errors,
                                            Result resultMessage, Frame frame, boolean schemaInAgreement,
                                            DefaultSession session, InternalDriverContext context,
                                            DriverExecutionProfile executionProfile) {
    final ByteBuffer pagingState =
            (resultMessage instanceof Rows) ? ((Rows) resultMessage).getMetadata().pagingState : null;
    return new DefaultExecutionInfo(request, coordinator, speculativeExecutionCount, successfulExecutionIndex, errors,
            pagingState, frame, schemaInAgreement, session, context, executionProfile);
  }

I was even thinking of changing the three create(...) methods into different names like, of, ofClientError, ofServerError. Thoughts?

Edit: Yet another option would be to introduce builder pattern for DefaultExecutionInfo class and might be a nicest solution.

public static Builder builder(
    Request request,
    Node coordinator,
    int speculativeExecutionCount,
    int successfulExecutionIndex,
    List<Map.Entry<Node, Throwable>> errors,
    DefaultSession session,
    InternalDriverContext context,
    DriverExecutionProfile executionProfile) {
  return new Builder(
      request,
      coordinator,
      speculativeExecutionCount,
      successfulExecutionIndex,
      errors,
      session,
      context,
      executionProfile);
}

public static class Builder {
  private final Request request;
  private final Node coordinator;
  private final int speculativeExecutionCount;
  private final int successfulExecutionIndex;
  private final List<Map.Entry<Node, Throwable>> errors;
  private final DefaultSession session;
  private final InternalDriverContext context;
  private final DriverExecutionProfile executionProfile;

  private Result response;
  private Frame frame;
  private boolean schemaInAgreement = true;

  public Builder(
      Request request,
      Node coordinator,
      int speculativeExecutionCount,
      int successfulExecutionIndex,
      List<Map.Entry<Node, Throwable>> errors,
      DefaultSession session,
      InternalDriverContext context,
      DriverExecutionProfile executionProfile) {
    this.request = request;
    this.coordinator = coordinator;
    this.speculativeExecutionCount = speculativeExecutionCount;
    this.successfulExecutionIndex = successfulExecutionIndex;
    this.errors = errors;
    this.session = session;
    this.context = context;
    this.executionProfile = executionProfile;
  }

  public Builder withServerResponse(Result response, Frame frame) {
    this.response = response;
    this.frame = frame;
    return this;
  }

  public Builder withServerResponse(Frame frame) {
    return withServerResponse(null, frame);
  }

  public Builder withSchemaInAgreement(boolean schemaInAgreement) {
    this.schemaInAgreement = schemaInAgreement;
    return this;
  }

  public DefaultExecutionInfo build() {
    final ByteBuffer pagingState =
        (response instanceof Rows) ? ((Rows) response).getMetadata().pagingState : null;
    return new DefaultExecutionInfo(
        request,
        coordinator,
        speculativeExecutionCount,
        successfulExecutionIndex,
        errors,
        pagingState,
        frame,
        schemaInAgreement,
        session,
        context,
        executionProfile);
  }
}

lukasz-antoniak avatar May 28 '24 14:05 lukasz-antoniak

A quick status update here: there's some overlap between the changes in this PR and some changes being proposed in @chibenwa's PR to minimize the number of resolutions of ExecutionProfile in CqlRequestHandler. Since that PR is quite a bit smaller and simpler I'd like to get it in first and then rebase this PR against those changes; doing it the other way around seems like a lot more work :). I've mentioned this relationship to the other committers on ASF Slack so hopefully we can get the other PR in quickly... we'll see how that goes.

absurdfarce avatar Jun 17 '24 22:06 absurdfarce

We realize that RequestTracker.getErrors was used for "errors from previous coordinators". We want to discuss whether we should still stick to that. I started the conversation in the ASF slack in the "#cassandra-drivers" channel. @ajweave you are welcome to join the conversation.

SiyaoIsHiding avatar Jun 20 '24 06:06 SiyaoIsHiding

@ajweave, @tolbertam, I have tried to solve my comments and submitted PR to Andrew's fork: https://github.com/ajweave/java-driver/pull/1. Could you please review the changes and comment? I am not enforcing any of those, just trying cleanup already existing code a little.

lukasz-antoniak avatar Jun 26 '24 11:06 lukasz-antoniak

@ajweave, @tolbertam, I have tried to solve my comments and submitted PR to Andrew's fork: ajweave#1. Could you please review the changes and comment? I am not enforcing any of those, just trying cleanup already existing code a little.

Thank you for pitching in. I will take a look.

ajweave avatar Jun 26 '24 20:06 ajweave