Skip to content

Commit d1b68ab

Browse files
hgromerHernan Gelaf-Romer
and
Hernan Gelaf-Romer
authoredApr 23, 2025
HBASE-29090: Add server-side load metrics to client results (#6623)
Co-authored-by: Hernan Gelaf-Romer <hgelafromer@hubspot.com> Signed-off-by: Nick Dimiduk <ndimiduk@apache.org> Signed-off-by: Ray Mattingly <rmattingly@apache.org>
1 parent f0e069e commit d1b68ab

File tree

21 files changed

+532
-58
lines changed

21 files changed

+532
-58
lines changed
 

‎hbase-client/src/main/java/org/apache/hadoop/hbase/client/CheckAndMutate.java

+42-12
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public static final class Builder {
7070
private byte[] value;
7171
private Filter filter;
7272
private TimeRange timeRange;
73+
private boolean queryMetricsEnabled = false;
7374

7475
private Builder(byte[] row) {
7576
this.row = Preconditions.checkNotNull(row, "row is null");
@@ -133,6 +134,21 @@ public Builder timeRange(TimeRange timeRange) {
133134
return this;
134135
}
135136

137+
/**
138+
* Enables the return of {@link QueryMetrics} alongside the corresponding result for this query
139+
* <p>
140+
* This is intended for advanced users who need result-granular, server-side metrics
141+
* <p>
142+
* Does not work
143+
* @param queryMetricsEnabled {@code true} to enable collection of per-result query metrics
144+
* {@code false} to disable metrics collection (resulting in
145+
* {@code null} metrics)
146+
*/
147+
public Builder queryMetricsEnabled(boolean queryMetricsEnabled) {
148+
this.queryMetricsEnabled = queryMetricsEnabled;
149+
return this;
150+
}
151+
136152
private void preCheck(Row action) {
137153
Preconditions.checkNotNull(action, "action is null");
138154
if (!Bytes.equals(row, action.getRow())) {
@@ -154,9 +170,10 @@ private void preCheck(Row action) {
154170
public CheckAndMutate build(Put put) {
155171
preCheck(put);
156172
if (filter != null) {
157-
return new CheckAndMutate(row, filter, timeRange, put);
173+
return new CheckAndMutate(row, filter, timeRange, put, queryMetricsEnabled);
158174
} else {
159-
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, put);
175+
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, put,
176+
queryMetricsEnabled);
160177
}
161178
}
162179

@@ -168,9 +185,10 @@ public CheckAndMutate build(Put put) {
168185
public CheckAndMutate build(Delete delete) {
169186
preCheck(delete);
170187
if (filter != null) {
171-
return new CheckAndMutate(row, filter, timeRange, delete);
188+
return new CheckAndMutate(row, filter, timeRange, delete, queryMetricsEnabled);
172189
} else {
173-
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, delete);
190+
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, delete,
191+
queryMetricsEnabled);
174192
}
175193
}
176194

@@ -182,9 +200,10 @@ public CheckAndMutate build(Delete delete) {
182200
public CheckAndMutate build(Increment increment) {
183201
preCheck(increment);
184202
if (filter != null) {
185-
return new CheckAndMutate(row, filter, timeRange, increment);
203+
return new CheckAndMutate(row, filter, timeRange, increment, queryMetricsEnabled);
186204
} else {
187-
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, increment);
205+
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, increment,
206+
queryMetricsEnabled);
188207
}
189208
}
190209

@@ -196,9 +215,10 @@ public CheckAndMutate build(Increment increment) {
196215
public CheckAndMutate build(Append append) {
197216
preCheck(append);
198217
if (filter != null) {
199-
return new CheckAndMutate(row, filter, timeRange, append);
218+
return new CheckAndMutate(row, filter, timeRange, append, queryMetricsEnabled);
200219
} else {
201-
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, append);
220+
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, append,
221+
queryMetricsEnabled);
202222
}
203223
}
204224

@@ -210,9 +230,10 @@ public CheckAndMutate build(Append append) {
210230
public CheckAndMutate build(RowMutations mutations) {
211231
preCheck(mutations);
212232
if (filter != null) {
213-
return new CheckAndMutate(row, filter, timeRange, mutations);
233+
return new CheckAndMutate(row, filter, timeRange, mutations, queryMetricsEnabled);
214234
} else {
215-
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, mutations);
235+
return new CheckAndMutate(row, family, qualifier, op, value, timeRange, mutations,
236+
queryMetricsEnabled);
216237
}
217238
}
218239
}
@@ -234,9 +255,10 @@ public static Builder newBuilder(byte[] row) {
234255
private final Filter filter;
235256
private final TimeRange timeRange;
236257
private final Row action;
258+
private final boolean queryMetricsEnabled;
237259

238260
private CheckAndMutate(byte[] row, byte[] family, byte[] qualifier, final CompareOperator op,
239-
byte[] value, TimeRange timeRange, Row action) {
261+
byte[] value, TimeRange timeRange, Row action, boolean queryMetricsEnabled) {
240262
this.row = row;
241263
this.family = family;
242264
this.qualifier = qualifier;
@@ -245,9 +267,11 @@ private CheckAndMutate(byte[] row, byte[] family, byte[] qualifier, final Compar
245267
this.filter = null;
246268
this.timeRange = timeRange != null ? timeRange : TimeRange.allTime();
247269
this.action = action;
270+
this.queryMetricsEnabled = queryMetricsEnabled;
248271
}
249272

250-
private CheckAndMutate(byte[] row, Filter filter, TimeRange timeRange, Row action) {
273+
private CheckAndMutate(byte[] row, Filter filter, TimeRange timeRange, Row action,
274+
boolean queryMetricsEnabled) {
251275
this.row = row;
252276
this.family = null;
253277
this.qualifier = null;
@@ -256,6 +280,7 @@ private CheckAndMutate(byte[] row, Filter filter, TimeRange timeRange, Row actio
256280
this.filter = filter;
257281
this.timeRange = timeRange != null ? timeRange : TimeRange.allTime();
258282
this.action = action;
283+
this.queryMetricsEnabled = queryMetricsEnabled;
259284
}
260285

261286
/** Returns the row */
@@ -303,4 +328,9 @@ public TimeRange getTimeRange() {
303328
public Row getAction() {
304329
return action;
305330
}
331+
332+
/** Returns whether query metrics are enabled */
333+
public boolean isQueryMetricsEnabled() {
334+
return queryMetricsEnabled;
335+
}
306336
}

‎hbase-client/src/main/java/org/apache/hadoop/hbase/client/CheckAndMutateResult.java

+11
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public class CheckAndMutateResult {
2727
private final boolean success;
2828
private final Result result;
2929

30+
private QueryMetrics metrics = null;
31+
3032
public CheckAndMutateResult(boolean success, Result result) {
3133
this.success = success;
3234
this.result = result;
@@ -41,4 +43,13 @@ public boolean isSuccess() {
4143
public Result getResult() {
4244
return result;
4345
}
46+
47+
public CheckAndMutateResult setMetrics(QueryMetrics metrics) {
48+
this.metrics = metrics;
49+
return this;
50+
}
51+
52+
public QueryMetrics getMetrics() {
53+
return metrics;
54+
}
4455
}

‎hbase-client/src/main/java/org/apache/hadoop/hbase/client/Get.java

+2
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public Get(Get get) {
9595
this.setFilter(get.getFilter());
9696
this.setReplicaId(get.getReplicaId());
9797
this.setConsistency(get.getConsistency());
98+
this.setQueryMetricsEnabled(get.isQueryMetricsEnabled());
9899
// from Get
99100
this.cacheBlocks = get.getCacheBlocks();
100101
this.maxVersions = get.getMaxVersions();
@@ -453,6 +454,7 @@ public Map<String, Object> toMap(int maxCols) {
453454
map.put("colFamTimeRangeMap", colFamTimeRangeMapStr);
454455
}
455456
map.put("priority", getPriority());
457+
map.put("queryMetricsEnabled", queryMetricsEnabled);
456458
return map;
457459
}
458460

‎hbase-client/src/main/java/org/apache/hadoop/hbase/client/Query.java

+24
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818
package org.apache.hadoop.hbase.client;
1919

20+
import java.util.List;
2021
import java.util.Map;
2122
import org.apache.hadoop.hbase.exceptions.DeserializationException;
2223
import org.apache.hadoop.hbase.filter.Filter;
@@ -46,6 +47,7 @@ public abstract class Query extends OperationWithAttributes {
4647
protected Consistency consistency = Consistency.STRONG;
4748
protected Map<byte[], TimeRange> colFamTimeRangeMap = Maps.newTreeMap(Bytes.BYTES_COMPARATOR);
4849
protected Boolean loadColumnFamiliesOnDemand = null;
50+
protected boolean queryMetricsEnabled = false;
4951

5052
public Filter getFilter() {
5153
return filter;
@@ -157,6 +159,28 @@ public Query setIsolationLevel(IsolationLevel level) {
157159
return this;
158160
}
159161

162+
/**
163+
* Enables the return of {@link QueryMetrics} alongside the corresponding result(s) for this query
164+
* <p>
165+
* This is intended for advanced users who need result-granular, server-side metrics
166+
* <p>
167+
* Does not work with calls to {@link Table#exists(Get)} and {@link Table#exists(List)}
168+
* @param enabled {@code true} to enable collection of per-result query metrics {@code false} to
169+
* disable metrics collection (resulting in {@code null} metrics)
170+
*/
171+
public Query setQueryMetricsEnabled(boolean enabled) {
172+
this.queryMetricsEnabled = enabled;
173+
return this;
174+
}
175+
176+
/**
177+
* Returns whether query metrics are enabled
178+
* @return {@code true} if query metrics are enabled, {@code false} otherwise
179+
*/
180+
public boolean isQueryMetricsEnabled() {
181+
return queryMetricsEnabled;
182+
}
183+
160184
/**
161185
* Returns The isolation level of this query. If no isolation level was set for this query object,
162186
* then it returns READ_COMMITTED.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.client;
19+
20+
import org.apache.yetus.audience.InterfaceAudience;
21+
import org.apache.yetus.audience.InterfaceStability;
22+
23+
@InterfaceAudience.Public
24+
@InterfaceStability.Evolving
25+
public class QueryMetrics {
26+
private final long blockBytesScanned;
27+
28+
public QueryMetrics(long blockBytesScanned) {
29+
this.blockBytesScanned = blockBytesScanned;
30+
}
31+
32+
@InterfaceStability.Evolving
33+
public long getBlockBytesScanned() {
34+
return blockBytesScanned;
35+
}
36+
}

‎hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncTableImpl.java

+10-8
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ public CompletableFuture<Boolean> thenPut(Put put) {
358358
() -> RawAsyncTableImpl.this.<Boolean> newCaller(row, put.getPriority(), rpcTimeoutNs)
359359
.action((controller, loc, stub) -> RawAsyncTableImpl.mutate(controller, loc, stub, put,
360360
(rn, p) -> RequestConverter.buildMutateRequest(rn, row, family, qualifier, op, value,
361-
null, timeRange, p, HConstants.NO_NONCE, HConstants.NO_NONCE),
361+
null, timeRange, p, HConstants.NO_NONCE, HConstants.NO_NONCE, false),
362362
(c, r) -> r.getProcessed()))
363363
.call(),
364364
supplier);
@@ -374,7 +374,7 @@ public CompletableFuture<Boolean> thenDelete(Delete delete) {
374374
() -> RawAsyncTableImpl.this.<Boolean> newCaller(row, delete.getPriority(), rpcTimeoutNs)
375375
.action((controller, loc, stub) -> RawAsyncTableImpl.mutate(controller, loc, stub, delete,
376376
(rn, d) -> RequestConverter.buildMutateRequest(rn, row, family, qualifier, op, value,
377-
null, timeRange, d, HConstants.NO_NONCE, HConstants.NO_NONCE),
377+
null, timeRange, d, HConstants.NO_NONCE, HConstants.NO_NONCE, false),
378378
(c, r) -> r.getProcessed()))
379379
.call(),
380380
supplier);
@@ -392,7 +392,7 @@ public CompletableFuture<Boolean> thenMutate(RowMutations mutations) {
392392
.action((controller, loc, stub) -> RawAsyncTableImpl.this.mutateRow(controller, loc, stub,
393393
mutations,
394394
(rn, rm) -> RequestConverter.buildMultiRequest(rn, row, family, qualifier, op, value,
395-
null, timeRange, rm, HConstants.NO_NONCE, HConstants.NO_NONCE),
395+
null, timeRange, rm, HConstants.NO_NONCE, HConstants.NO_NONCE, false),
396396
CheckAndMutateResult::isSuccess))
397397
.call(), supplier);
398398
}
@@ -433,7 +433,7 @@ public CompletableFuture<Boolean> thenPut(Put put) {
433433
() -> RawAsyncTableImpl.this.<Boolean> newCaller(row, put.getPriority(), rpcTimeoutNs)
434434
.action((controller, loc, stub) -> RawAsyncTableImpl.mutate(controller, loc, stub, put,
435435
(rn, p) -> RequestConverter.buildMutateRequest(rn, row, null, null, null, null, filter,
436-
timeRange, p, HConstants.NO_NONCE, HConstants.NO_NONCE),
436+
timeRange, p, HConstants.NO_NONCE, HConstants.NO_NONCE, false),
437437
(c, r) -> r.getProcessed()))
438438
.call(),
439439
supplier);
@@ -448,7 +448,7 @@ public CompletableFuture<Boolean> thenDelete(Delete delete) {
448448
() -> RawAsyncTableImpl.this.<Boolean> newCaller(row, delete.getPriority(), rpcTimeoutNs)
449449
.action((controller, loc, stub) -> RawAsyncTableImpl.mutate(controller, loc, stub, delete,
450450
(rn, d) -> RequestConverter.buildMutateRequest(rn, row, null, null, null, null, filter,
451-
timeRange, d, HConstants.NO_NONCE, HConstants.NO_NONCE),
451+
timeRange, d, HConstants.NO_NONCE, HConstants.NO_NONCE, false),
452452
(c, r) -> r.getProcessed()))
453453
.call(),
454454
supplier);
@@ -465,7 +465,7 @@ public CompletableFuture<Boolean> thenMutate(RowMutations mutations) {
465465
.action((controller, loc, stub) -> RawAsyncTableImpl.this.mutateRow(controller, loc, stub,
466466
mutations,
467467
(rn, rm) -> RequestConverter.buildMultiRequest(rn, row, null, null, null, null, filter,
468-
timeRange, rm, HConstants.NO_NONCE, HConstants.NO_NONCE),
468+
timeRange, rm, HConstants.NO_NONCE, HConstants.NO_NONCE, false),
469469
CheckAndMutateResult::isSuccess))
470470
.call(), supplier);
471471
}
@@ -500,7 +500,8 @@ public CompletableFuture<CheckAndMutateResult> checkAndMutate(CheckAndMutate che
500500
(rn, m) -> RequestConverter.buildMutateRequest(rn, checkAndMutate.getRow(),
501501
checkAndMutate.getFamily(), checkAndMutate.getQualifier(),
502502
checkAndMutate.getCompareOp(), checkAndMutate.getValue(),
503-
checkAndMutate.getFilter(), checkAndMutate.getTimeRange(), m, nonceGroup, nonce),
503+
checkAndMutate.getFilter(), checkAndMutate.getTimeRange(), m, nonceGroup, nonce,
504+
checkAndMutate.isQueryMetricsEnabled()),
504505
(c, r) -> ResponseConverter.getCheckAndMutateResult(r, c.cellScanner())))
505506
.call();
506507
} else if (checkAndMutate.getAction() instanceof RowMutations) {
@@ -516,7 +517,8 @@ CheckAndMutateResult> mutateRow(controller, loc, stub, rowMutations,
516517
(rn, rm) -> RequestConverter.buildMultiRequest(rn, checkAndMutate.getRow(),
517518
checkAndMutate.getFamily(), checkAndMutate.getQualifier(),
518519
checkAndMutate.getCompareOp(), checkAndMutate.getValue(),
519-
checkAndMutate.getFilter(), checkAndMutate.getTimeRange(), rm, nonceGroup, nonce),
520+
checkAndMutate.getFilter(), checkAndMutate.getTimeRange(), rm, nonceGroup, nonce,
521+
checkAndMutate.isQueryMetricsEnabled()),
520522
resp -> resp))
521523
.call();
522524
} else {

‎hbase-client/src/main/java/org/apache/hadoop/hbase/client/Result.java

+11
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public class Result implements ExtendedCellScannable, ExtendedCellScanner {
9999
*/
100100
private int cellScannerIndex = INITIAL_CELLSCANNER_INDEX;
101101
private RegionLoadStats stats;
102+
private QueryMetrics metrics = null;
102103

103104
private final boolean readonly;
104105

@@ -931,6 +932,11 @@ public void setStatistics(RegionLoadStats loadStats) {
931932
this.stats = loadStats;
932933
}
933934

935+
@InterfaceAudience.Private
936+
public void setMetrics(QueryMetrics metrics) {
937+
this.metrics = metrics;
938+
}
939+
934940
/**
935941
* Returns the associated statistics about the region from which this was returned. Can be
936942
* <tt>null</tt> if stats are disabled.
@@ -939,6 +945,11 @@ public RegionLoadStats getStats() {
939945
return stats;
940946
}
941947

948+
/** Returns the query metrics, or {@code null} if we do not enable metrics. */
949+
public QueryMetrics getMetrics() {
950+
return metrics;
951+
}
952+
942953
/**
943954
* All methods modifying state of Result object must call this method to ensure that special
944955
* purpose immutable Results can't be accidentally modified.

‎hbase-client/src/main/java/org/apache/hadoop/hbase/client/Scan.java

+3
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ public Scan(Scan scan) throws IOException {
217217
setPriority(scan.getPriority());
218218
readType = scan.getReadType();
219219
super.setReplicaId(scan.getReplicaId());
220+
super.setQueryMetricsEnabled(scan.isQueryMetricsEnabled());
220221
}
221222

222223
/**
@@ -249,6 +250,7 @@ public Scan(Get get) {
249250
this.mvccReadPoint = -1L;
250251
setPriority(get.getPriority());
251252
super.setReplicaId(get.getReplicaId());
253+
super.setQueryMetricsEnabled(get.isQueryMetricsEnabled());
252254
}
253255

254256
public boolean isGetScan() {
@@ -826,6 +828,7 @@ public Map<String, Object> toMap(int maxCols) {
826828
map.put("colFamTimeRangeMap", colFamTimeRangeMapStr);
827829
}
828830
map.put("priority", getPriority());
831+
map.put("queryMetricsEnabled", queryMetricsEnabled);
829832
return map;
830833
}
831834

0 commit comments

Comments
 (0)
Please sign in to comment.