-
Notifications
You must be signed in to change notification settings - Fork 3.4k
HBASE-29090: Add server-side load metrics to client results #6623
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.apache.hadoop.hbase.client; | ||
|
|
||
| import org.apache.yetus.audience.InterfaceAudience; | ||
| import org.apache.yetus.audience.InterfaceStability; | ||
|
|
||
| @InterfaceAudience.Public | ||
| @InterfaceStability.Evolving | ||
| public class QueryMetrics { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also decorate this class with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use the existing ServerSideMetric class? It has loose typing for metrics via a map and protobuf logic is already there. The only advantage for having QueryMetrics is that the metrics as fields will be more efficient than a map, but it can be improved by defining predefined metric "schemas" for ServerSideMetric. E.g., you can have "QueryMetrics" as one of the schemas and it would predefine a fixed size counters map with "blockBytesScanned" as the only metric.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ServerSideMetric currently has a fixed schema of counters, but this can be changed such that a different schema can be specified.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We at Salesforce have a similar requirement to track scan metrics at a higher granularity of region level. Currently scan metrics for all the regions scanned are combined together as they are available and so we lose the granularity. Thus, we have a proposal to maintain the per region metrics along with capturing region name and RS where the region lies. A new method will be exposed to retrieve the metrics with granularity, but the existing method will behave the same as it combines all the per region metrics into one structure. We are reusing the existing ScanMetrics class due to following reasons:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm hesitant to do this. What's the value in obfuscating the actual metrics themselves by using a generic map when we can efficiently and more clearly represent whichever fields are being served to the user? In my opinion, this is also less error-prone, as the fields are clearly stated, and it's harder to remove or modify something unintentionally.
I talked a little bit about why I opted for creating a new class, rather than re-using an existing one here. I worry about coupling the new metrics and Scans with this shared metrics class which could make it hard to iterate in the future. It makes sense to re-use the ScanMetrics for more granular scan metrics, but I think it makes sense to create a new metric type for metrics that will back both Get(s) and Scans. Additionally, the granularity is so course, that we'll be adding bloat to the metric by requiring fields, such as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll still need to introduce this new pattern to enable metrics at the granularity that we want (per-result). So I don't think re-using a base class, and implementing an inheritance hierarchy avoids this. I'm not sure I see the benefit of avoiding a new protobuf definition, or avoiding a new converter. Both are pretty trivial to create, and allow us to keep these concepts (result metrics, vs aggregated scan metrics) separate. Perhaps there's some value in a base metrics class that simply stores counters, it requires a bit of refactoring and touches existing code. For the sake of discussion, I took a stab at what it would look if we did create this inheritance hierarchy. We can look at this commit to compare both implementations. I'm not we get much value from it, but am happy to continue discussing.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Agreed with @hgromer. I think these are two separate things, and there's not a lot of value in shoehorning inheritance. Wishy washy inheritance feels like something we'll wish we hadn't done two or three iterations down the road
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I am not sure if I am missing the point, but of course, how else will you get instrumentation other than actually using the class?
The suggestion was about reusing the existing pattern by making it more generic, instead of introducing a new class. If we keep adding new standalone classes for every new scenario, it can quickly get confusing. Also, the existing counter map based approach is easier to introspect and process. Just my 2¢.
Looks good, but I would rename
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel that the original implementation is still far cleaner. We need to add a QueryMetric class regardless; the only thing we save on is an additional protobuf, but I don't think that's a huge benefit. I think generic counters make sense when the schema or shape of the data is abstract, or unknown (such as Configuration), but I think it's neater to leverage explicit typings whenever possible.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've reverted back |
||
| private final long blockBytesScanned; | ||
|
|
||
| public QueryMetrics(long blockBytesScanned) { | ||
| this.blockBytesScanned = blockBytesScanned; | ||
| } | ||
|
|
||
| @InterfaceStability.Evolving | ||
| public long getBlockBytesScanned() { | ||
| return blockBytesScanned; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -358,7 +358,7 @@ public CompletableFuture<Boolean> thenPut(Put put) { | |
| () -> RawAsyncTableImpl.this.<Boolean> newCaller(row, put.getPriority(), rpcTimeoutNs) | ||
| .action((controller, loc, stub) -> RawAsyncTableImpl.mutate(controller, loc, stub, put, | ||
| (rn, p) -> RequestConverter.buildMutateRequest(rn, row, family, qualifier, op, value, | ||
| null, timeRange, p, HConstants.NO_NONCE, HConstants.NO_NONCE), | ||
| null, timeRange, p, HConstants.NO_NONCE, HConstants.NO_NONCE, false), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These methods correspond with the deprecated
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A carrot to encourage folks to move on :) |
||
| (c, r) -> r.getProcessed())) | ||
| .call(), | ||
| supplier); | ||
|
|
@@ -374,7 +374,7 @@ public CompletableFuture<Boolean> thenDelete(Delete delete) { | |
| () -> RawAsyncTableImpl.this.<Boolean> newCaller(row, delete.getPriority(), rpcTimeoutNs) | ||
| .action((controller, loc, stub) -> RawAsyncTableImpl.mutate(controller, loc, stub, delete, | ||
| (rn, d) -> RequestConverter.buildMutateRequest(rn, row, family, qualifier, op, value, | ||
| null, timeRange, d, HConstants.NO_NONCE, HConstants.NO_NONCE), | ||
| null, timeRange, d, HConstants.NO_NONCE, HConstants.NO_NONCE, false), | ||
| (c, r) -> r.getProcessed())) | ||
| .call(), | ||
| supplier); | ||
|
|
@@ -392,7 +392,7 @@ public CompletableFuture<Boolean> thenMutate(RowMutations mutations) { | |
| .action((controller, loc, stub) -> RawAsyncTableImpl.this.mutateRow(controller, loc, stub, | ||
| mutations, | ||
| (rn, rm) -> RequestConverter.buildMultiRequest(rn, row, family, qualifier, op, value, | ||
| null, timeRange, rm, HConstants.NO_NONCE, HConstants.NO_NONCE), | ||
| null, timeRange, rm, HConstants.NO_NONCE, HConstants.NO_NONCE, false), | ||
| CheckAndMutateResult::isSuccess)) | ||
| .call(), supplier); | ||
| } | ||
|
|
@@ -433,7 +433,7 @@ public CompletableFuture<Boolean> thenPut(Put put) { | |
| () -> RawAsyncTableImpl.this.<Boolean> newCaller(row, put.getPriority(), rpcTimeoutNs) | ||
| .action((controller, loc, stub) -> RawAsyncTableImpl.mutate(controller, loc, stub, put, | ||
| (rn, p) -> RequestConverter.buildMutateRequest(rn, row, null, null, null, null, filter, | ||
| timeRange, p, HConstants.NO_NONCE, HConstants.NO_NONCE), | ||
| timeRange, p, HConstants.NO_NONCE, HConstants.NO_NONCE, false), | ||
| (c, r) -> r.getProcessed())) | ||
| .call(), | ||
| supplier); | ||
|
|
@@ -448,7 +448,7 @@ public CompletableFuture<Boolean> thenDelete(Delete delete) { | |
| () -> RawAsyncTableImpl.this.<Boolean> newCaller(row, delete.getPriority(), rpcTimeoutNs) | ||
| .action((controller, loc, stub) -> RawAsyncTableImpl.mutate(controller, loc, stub, delete, | ||
| (rn, d) -> RequestConverter.buildMutateRequest(rn, row, null, null, null, null, filter, | ||
| timeRange, d, HConstants.NO_NONCE, HConstants.NO_NONCE), | ||
| timeRange, d, HConstants.NO_NONCE, HConstants.NO_NONCE, false), | ||
| (c, r) -> r.getProcessed())) | ||
| .call(), | ||
| supplier); | ||
|
|
@@ -465,7 +465,7 @@ public CompletableFuture<Boolean> thenMutate(RowMutations mutations) { | |
| .action((controller, loc, stub) -> RawAsyncTableImpl.this.mutateRow(controller, loc, stub, | ||
| mutations, | ||
| (rn, rm) -> RequestConverter.buildMultiRequest(rn, row, null, null, null, null, filter, | ||
| timeRange, rm, HConstants.NO_NONCE, HConstants.NO_NONCE), | ||
| timeRange, rm, HConstants.NO_NONCE, HConstants.NO_NONCE, false), | ||
| CheckAndMutateResult::isSuccess)) | ||
| .call(), supplier); | ||
| } | ||
|
|
@@ -500,7 +500,8 @@ public CompletableFuture<CheckAndMutateResult> checkAndMutate(CheckAndMutate che | |
| (rn, m) -> RequestConverter.buildMutateRequest(rn, checkAndMutate.getRow(), | ||
| checkAndMutate.getFamily(), checkAndMutate.getQualifier(), | ||
| checkAndMutate.getCompareOp(), checkAndMutate.getValue(), | ||
| checkAndMutate.getFilter(), checkAndMutate.getTimeRange(), m, nonceGroup, nonce), | ||
| checkAndMutate.getFilter(), checkAndMutate.getTimeRange(), m, nonceGroup, nonce, | ||
| checkAndMutate.isQueryMetricsEnabled()), | ||
| (c, r) -> ResponseConverter.getCheckAndMutateResult(r, c.cellScanner()))) | ||
| .call(); | ||
| } else if (checkAndMutate.getAction() instanceof RowMutations) { | ||
|
|
@@ -516,7 +517,8 @@ CheckAndMutateResult> mutateRow(controller, loc, stub, rowMutations, | |
| (rn, rm) -> RequestConverter.buildMultiRequest(rn, checkAndMutate.getRow(), | ||
| checkAndMutate.getFamily(), checkAndMutate.getQualifier(), | ||
| checkAndMutate.getCompareOp(), checkAndMutate.getValue(), | ||
| checkAndMutate.getFilter(), checkAndMutate.getTimeRange(), rm, nonceGroup, nonce), | ||
| checkAndMutate.getFilter(), checkAndMutate.getTimeRange(), rm, nonceGroup, nonce, | ||
| checkAndMutate.isQueryMetricsEnabled()), | ||
| resp -> resp)) | ||
| .call(); | ||
| } else { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sadly, because the
Resultcan be null, we need to stash theQueryMetricsseparately.