This is an automated email from the ASF dual-hosted git repository.

charlesconnell pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-3.0 by this push:
     new 3a65a696afc HBASE-30332 Preserve QueryMetrics for empty results (#8555)
3a65a696afc is described below

commit 3a65a696afc91f79f589fdc3c6cc6ea30e2402c9
Author: Ma Zhengxuan <[email protected]>
AuthorDate: Tue Aug 25 06:47:56 2026 +0800

    HBASE-30332 Preserve QueryMetrics for empty results (#8555)
    
    Co-authored-by: mazhengxuan <[email protected]>
    Signed-off by: Peng Lu <[email protected]>
    Signed-off by: Charles Connell <[email protected]>
---
 .../hadoop/hbase/shaded/protobuf/ProtobufUtil.java | 55 +++++++++++++++++-----
 .../hbase/shaded/protobuf/TestProtobufUtil.java    | 38 +++++++++++++++
 .../hadoop/hbase/regionserver/RSRpcServices.java   |  4 +-
 .../hbase/client/TestAsyncTableQueryMetrics.java   | 19 ++++++++
 4 files changed, 102 insertions(+), 14 deletions(-)

diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
index 60175137ad2..fab8436ab12 100644
--- 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
+++ 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
@@ -1423,12 +1423,13 @@ public final class ProtobufUtil {
    */
   public static ClientProtos.Result toResult(final Result result, boolean 
encodeTags) {
     if (result.getExists() != null) {
-      return toResult(result.getExists(), result.isStale());
+      return toResult(result.getExists(), result.isStale(), 
result.getMetrics());
     }
 
     ExtendedCell[] cells = ClientInternalHelper.getExtendedRawCells(result);
     if (cells == null || cells.length == 0) {
-      return result.isStale() ? EMPTY_RESULT_PB_STALE : EMPTY_RESULT_PB;
+      return withMetrics(result.isStale() ? EMPTY_RESULT_PB_STALE : 
EMPTY_RESULT_PB,
+        result.getMetrics());
     }
 
     ClientProtos.Result.Builder builder = ClientProtos.Result.newBuilder();
@@ -1459,6 +1460,24 @@ public final class ProtobufUtil {
     }
   }
 
+  /**
+   * Convert a client Result to a protocol buffer Result
+   * @param existence the client existence to send
+   * @param stale     whether the result is stale
+   * @param metrics   query metrics associated with the result
+   * @return the converted protocol buffer Result
+   */
+  public static ClientProtos.Result toResult(final boolean existence, boolean 
stale,
+    QueryMetrics metrics) {
+    return withMetrics(toResult(existence, stale), metrics);
+  }
+
+  private static ClientProtos.Result withMetrics(ClientProtos.Result result, 
QueryMetrics metrics) {
+    return metrics == null
+      ? result
+      : result.toBuilder().setMetrics(toQueryMetrics(metrics)).build();
+  }
+
   /**
    * Convert a client Result to a protocol buffer Result. The pb Result does 
not include the Cell
    * data. That is for transport otherwise.
@@ -1466,9 +1485,14 @@ public final class ProtobufUtil {
    * @return the converted protocol buffer Result
    */
   public static ClientProtos.Result toResultNoData(final Result result) {
-    if (result.getExists() != null) return toResult(result.getExists(), 
result.isStale());
+    if (result.getExists() != null) {
+      return toResult(result.getExists(), result.isStale(), 
result.getMetrics());
+    }
     int size = result.size();
-    if (size == 0) return result.isStale() ? EMPTY_RESULT_PB_STALE : 
EMPTY_RESULT_PB;
+    if (size == 0) {
+      return withMetrics(result.isStale() ? EMPTY_RESULT_PB_STALE : 
EMPTY_RESULT_PB,
+        result.getMetrics());
+    }
     ClientProtos.Result.Builder builder = ClientProtos.Result.newBuilder();
     builder.setAssociatedCellCount(size);
     builder.setStale(result.isStale());
@@ -1499,6 +1523,11 @@ public final class ProtobufUtil {
    */
   public static Result toResult(final ClientProtos.Result proto, boolean 
decodeTags) {
     if (proto.hasExists()) {
+      if (proto.hasMetrics()) {
+        Result result = Result.create((Cell[]) null, proto.getExists(), 
proto.getStale());
+        result.setMetrics(toQueryMetrics(proto.getMetrics()));
+        return result;
+      }
       if (proto.getStale()) {
         return proto.getExists() ? EMPTY_RESULT_EXISTS_TRUE_STALE : 
EMPTY_RESULT_EXISTS_FALSE_STALE;
       }
@@ -1506,7 +1535,7 @@ public final class ProtobufUtil {
     }
 
     List<CellProtos.Cell> values = proto.getCellList();
-    if (values.isEmpty()) {
+    if (values.isEmpty() && !proto.hasMetrics()) {
       return proto.getStale() ? EMPTY_RESULT_STALE : EMPTY_RESULT;
     }
 
@@ -1539,10 +1568,7 @@ public final class ProtobufUtil {
       ) {
         throw new IllegalArgumentException("bad proto: exists with cells is no 
allowed " + proto);
       }
-      if (proto.getStale()) {
-        return proto.getExists() ? EMPTY_RESULT_EXISTS_TRUE_STALE : 
EMPTY_RESULT_EXISTS_FALSE_STALE;
-      }
-      return proto.getExists() ? EMPTY_RESULT_EXISTS_TRUE : 
EMPTY_RESULT_EXISTS_FALSE;
+      return toResult(proto);
     }
 
     // TODO: Unit test that has some Cells in scanner and some in the proto.
@@ -1564,9 +1590,14 @@ public final class ProtobufUtil {
       }
     }
 
-    Result r = (cells == null || cells.isEmpty())
-      ? (proto.getStale() ? EMPTY_RESULT_STALE : EMPTY_RESULT)
-      : Result.create(cells, null, proto.getStale());
+    Result r;
+    if (cells == null || cells.isEmpty()) {
+      r = proto.hasMetrics()
+        ? Result.create(EMPTY_CELL_ARRAY, null, proto.getStale())
+        : (proto.getStale() ? EMPTY_RESULT_STALE : EMPTY_RESULT);
+    } else {
+      r = Result.create(cells, null, proto.getStale());
+    }
 
     if (proto.hasMetrics()) {
       r.setMetrics(toQueryMetrics(proto.getMetrics()));
diff --git 
a/hbase-client/src/test/java/org/apache/hadoop/hbase/shaded/protobuf/TestProtobufUtil.java
 
b/hbase-client/src/test/java/org/apache/hadoop/hbase/shaded/protobuf/TestProtobufUtil.java
index 6326bf7fd77..4bad7faf1a0 100644
--- 
a/hbase-client/src/test/java/org/apache/hadoop/hbase/shaded/protobuf/TestProtobufUtil.java
+++ 
b/hbase-client/src/test/java/org/apache/hadoop/hbase/shaded/protobuf/TestProtobufUtil.java
@@ -19,11 +19,13 @@ package org.apache.hadoop.hbase.shaded.protobuf;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import org.apache.hadoop.hbase.ArrayBackedTag;
@@ -43,6 +45,8 @@ import org.apache.hadoop.hbase.client.Delete;
 import org.apache.hadoop.hbase.client.Get;
 import org.apache.hadoop.hbase.client.Increment;
 import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.QueryMetrics;
+import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.SlowLogParams;
 import org.apache.hadoop.hbase.io.TimeRange;
 import org.apache.hadoop.hbase.testclassification.SmallTests;
@@ -132,6 +136,40 @@ public class TestProtobufUtil {
     assertEquals(getBuilder.build(), ProtobufUtil.toGet(get));
   }
 
+  @Test
+  public void testEmptyResultWithQueryMetrics() throws IOException {
+    long blockBytesScanned = 123L;
+    for (Boolean exists : Arrays.asList(null, false, true)) {
+      Result result = Result.create(Collections.emptyList(), exists);
+      result.setMetrics(new QueryMetrics(blockBytesScanned));
+
+      for (ClientProtos.Result proto : List.of(ProtobufUtil.toResult(result),
+        ProtobufUtil.toResultNoData(result))) {
+        assertEquals(exists, proto.hasExists() ? proto.getExists() : null);
+        assertTrue(proto.hasMetrics());
+        assertEquals(blockBytesScanned, 
proto.getMetrics().getBlockBytesScanned());
+
+        Result roundTrip = ProtobufUtil.toResult(proto);
+        assertEquals(exists, roundTrip.getExists());
+        assertNotNull(roundTrip.getMetrics());
+        assertEquals(blockBytesScanned, 
roundTrip.getMetrics().getBlockBytesScanned());
+
+        roundTrip = ProtobufUtil.toResult(proto,
+          PrivateCellUtil.createExtendedCellScanner(Collections.<ExtendedCell> 
emptyList()));
+        assertEquals(exists, roundTrip.getExists());
+        assertNotNull(roundTrip.getMetrics());
+        assertEquals(blockBytesScanned, 
roundTrip.getMetrics().getBlockBytesScanned());
+      }
+    }
+
+    ClientProtos.Result emptyProto = ClientProtos.Result.getDefaultInstance();
+    assertNull(ProtobufUtil.toResult(emptyProto).getMetrics());
+    assertNull(ProtobufUtil
+      .toResult(emptyProto,
+        PrivateCellUtil.createExtendedCellScanner(Collections.<ExtendedCell> 
emptyList()))
+      .getMetrics());
+  }
+
   /**
    * Test Delete Mutate conversions.
    * @throws IOException if the conversion to a {@link Delete} or a
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java
index fdfea375e09..24dde9f8e0e 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RSRpcServices.java
@@ -2532,8 +2532,8 @@ public class RSRpcServices extends 
HBaseRpcServicesBase<HRegionServer>
         }
       }
       if (existence != null) {
-        ClientProtos.Result pbr =
-          ProtobufUtil.toResult(existence, 
region.getRegionInfo().getReplicaId() != 0);
+        ClientProtos.Result pbr = ProtobufUtil.toResult(existence,
+          region.getRegionInfo().getReplicaId() != 0, r != null ? 
r.getMetrics() : null);
         builder.setResult(pbr);
       } else if (r != null) {
         ClientProtos.Result pbr;
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableQueryMetrics.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableQueryMetrics.java
index 72591c19d79..7d79d94fe53 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableQueryMetrics.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncTableQueryMetrics.java
@@ -108,6 +108,25 @@ public class TestAsyncTableQueryMetrics {
     }
 
     assertEquals(getClusterBlockBytesScanned(), bbs);
+
+    g1.setCheckExistenceOnly(true);
+    g2.setCheckExistenceOnly(true);
+    g3.setCheckExistenceOnly(true);
+
+    result = CONN.getTable(TABLE_NAME).get(g1).get();
+    assertEquals(Boolean.TRUE, result.getExists());
+    assertNotNull(result.getMetrics());
+    bbs += result.getMetrics().getBlockBytesScanned();
+    assertEquals(getClusterBlockBytesScanned(), bbs);
+
+    futures = CONN.getTable(TABLE_NAME).get(List.of(g1, g2, g3));
+    for (CompletableFuture<Result> future : futures) {
+      result = future.join();
+      assertEquals(Boolean.TRUE, result.getExists());
+      assertNotNull(result.getMetrics());
+      bbs += result.getMetrics().getBlockBytesScanned();
+    }
+    assertEquals(getClusterBlockBytesScanned(), bbs);
   }
 
   @Test

Reply via email to