From d22a2410a384d8c0dc22f9e414b4d7fa7ebebfa9 Mon Sep 17 00:00:00 2001 From: JackShi148 Date: Fri, 24 Oct 2025 15:49:46 +0800 Subject: [PATCH 01/15] add metrics and callback --- .../com/alipay/oceanbase/hbase/OHTable.java | 241 ++++++++++++++---- .../oceanbase/hbase/util/MetricsExporter.java | 105 ++++++++ .../oceanbase/hbase/util/MetricsImporter.java | 37 +++ .../oceanbase/hbase/util/OHBaseFuncUtils.java | 16 ++ .../oceanbase/hbase/util/OHMetrics.java | 73 ++++++ .../hbase/util/OHMetricsTracker.java | 53 ++++ 6 files changed, 474 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java create mode 100644 src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java create mode 100644 src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java create mode 100644 src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index 5efbb7f7..549c09dc 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -27,6 +27,7 @@ import com.alipay.oceanbase.rpc.ObTableClient; import com.alipay.oceanbase.rpc.exception.ObTableException; import com.alipay.oceanbase.rpc.exception.ObTableUnexpectedException; +import com.alipay.oceanbase.rpc.location.model.partition.ObPair; import com.alipay.oceanbase.rpc.location.model.partition.Partition; import com.alipay.oceanbase.rpc.mutation.BatchOperation; import com.alipay.oceanbase.rpc.mutation.result.BatchOperationResult; @@ -78,7 +79,6 @@ import static com.alipay.oceanbase.hbase.util.TableHBaseLoggerFactory.TABLE_HBASE_LOGGER_SPACE; import static com.alipay.oceanbase.rpc.mutation.MutationFactory.colVal; import static com.alipay.oceanbase.rpc.mutation.MutationFactory.row; -import static com.alipay.oceanbase.rpc.property.Property.RPC_OPERATION_TIMEOUT; import static com.alipay.oceanbase.rpc.protocol.payload.impl.execute.ObTableOperation.getInstance; import static com.alipay.oceanbase.rpc.protocol.payload.impl.execute.ObTableOperationType.*; import static com.alipay.sofa.common.thread.SofaThreadPoolConstants.SOFA_THREAD_POOL_LOGGING_CAPABILITY; @@ -86,6 +86,7 @@ import static org.apache.commons.lang.StringUtils.isBlank; import static org.apache.commons.lang.StringUtils.isNotBlank; import static com.alipay.oceanbase.hbase.filter.HBaseFilterUtils.writeBytesWithEscape; +import static org.apache.hadoop.hbase.client.MetricsConnection.CLIENT_SIDE_METRICS_ENABLED_KEY; public class OHTable implements Table { @@ -198,6 +199,8 @@ public class OHTable implements Table { private RegionLocator regionLocator; + private final OHMetrics metrics; + /** * Creates an object to access a HBase table. * Shares oceanbase table obTableClient and other resources with other OHTable instances @@ -229,6 +232,13 @@ public OHTable(Configuration configuration, String tableName) throws IOException this.obTableClient.setRpcExecuteTimeout(ohConnectionConf.getRpcTimeout()); this.obTableClient.setRuntimeRetryTimes(numRetries); setOperationTimeout(ohConnectionConf.getClientOperationTimeout()); + if (configuration.getBoolean(CLIENT_SIDE_METRICS_ENABLED_KEY, false)) { + this.metrics = new OHMetrics(OHBaseFuncUtils.metricsNameBuilder(tableNameString, + obTableClient.getDatabase(), + obTableClient.getClusterName())); + } else { + this.metrics = null; + } finishSetUp(); } @@ -281,6 +291,13 @@ public OHTable(Configuration configuration, final byte[] tableName, this.obTableClient.setRpcExecuteTimeout(ohConnectionConf.getRpcTimeout()); this.obTableClient.setRuntimeRetryTimes(numRetries); setOperationTimeout(ohConnectionConf.getClientOperationTimeout()); + if (configuration.getBoolean(CLIENT_SIDE_METRICS_ENABLED_KEY, false)) { + this.metrics = new OHMetrics(OHBaseFuncUtils.metricsNameBuilder(tableNameString, + obTableClient.getDatabase(), + obTableClient.getClusterName())); + } else { + this.metrics = null; + } finishSetUp(); } @@ -310,6 +327,7 @@ public OHTable(final byte[] tableName, final ObTableClient obTableClient, this.executePool = executePool; this.obTableClient = obTableClient; this.configuration = new Configuration(); + this.metrics = null; finishSetUp(); } @@ -352,6 +370,13 @@ public OHTable(TableName tableName, Connection connection, this.obTableClient.setRpcExecuteTimeout(rpcTimeout); this.obTableClient.setRuntimeRetryTimes(numRetries); setOperationTimeout(operationTimeout); + if (configuration.getBoolean(CLIENT_SIDE_METRICS_ENABLED_KEY, false)) { + this.metrics = new OHMetrics(OHBaseFuncUtils.metricsNameBuilder(tableNameString, + obTableClient.getDatabase(), + obTableClient.getClusterName())); + } else { + this.metrics = null; + } finishSetUp(); } @@ -395,6 +420,13 @@ public OHTable(Connection connection, ObTableBuilderBase builder, this.obTableClient.setRpcExecuteTimeout(rpcTimeout); this.obTableClient.setRuntimeRetryTimes(numRetries); setOperationTimeout(operationTimeout); + if (configuration.getBoolean(CLIENT_SIDE_METRICS_ENABLED_KEY, false)) { + this.metrics = new OHMetrics(OHBaseFuncUtils.metricsNameBuilder(tableNameString, + obTableClient.getDatabase(), + obTableClient.getClusterName())); + } else { + this.metrics = null; + } finishSetUp(); } @@ -491,6 +523,49 @@ public static OHConnectionConfiguration setUserDefinedNamespace(String tableName return ohConnectionConf; } + private abstract class OperationExecuteCallback { + private final OHOperationType opType; + private final long singleOpCount; + OperationExecuteCallback(OHOperationType opType, long singleOpCount) { + this.opType = opType; + this.singleOpCount = singleOpCount; + } + abstract T execute() throws IOException; + + public OHOperationType getOpType() { + return this.opType; + } + + public long getSingleOpCount() { + return this.singleOpCount; + } + } + + private T execute(OperationExecuteCallback callback) throws IOException { + if (this.metrics != null) { + long startTimeMs = System.currentTimeMillis(); + MetricsImporter importer = new MetricsImporter(); + importer.setSingleOpCount(callback.getSingleOpCount()); + try { + return callback.execute(); + } catch (Exception e) { + // do not deal with any exception, just record + importer.setIsFailedOp(true); // set as failed op + if (e instanceof IOException) { + throw e; + } else { + throw new IOException("meet non-IOException in callback execute", e); + } + } finally { + long duration = System.currentTimeMillis() - startTimeMs; + importer.setDuration(duration); + this.metrics.update(new ObPair(callback.getOpType(), importer)); + } + } else { + return callback.execute(); + } + } + @Override public TableName getName() { return TableName.valueOf(tableNameString); @@ -528,9 +603,14 @@ public TableDescriptor getDescriptor() throws IOException { */ @Override public boolean exists(Get get) throws IOException { - Get newGet = new Get(get); - newGet.setCheckExistenceOnly(true); - return this.get(newGet).getExists(); + return execute(new OperationExecuteCallback(OHOperationType.EXISTS, 1) { + @Override + Boolean execute() throws IOException { + Get newGet = new Get(get); + newGet.setCheckExistenceOnly(true); + return innerGetImpl(newGet).getExists(); + } + }); } @Override @@ -617,6 +697,7 @@ private BatchOperation compatOldServerPut(final List actions, fin private BatchOperation compatOldServerDel(final List actions, final Object[] results, BatchError batchError, int i) throws Exception { Delete delete = (Delete)actions.get(i); + checkArgument(delete.getRow() != null, "row is null"); List resultMapSingleOp = new LinkedList<>(); if (delete.isEmpty()) { return buildBatchOperation(tableNameString, @@ -705,6 +786,17 @@ private void compatOldServerBatch(final List actions, final Objec @Override public void batch(final List actions, final Object[] results) throws IOException { + OHOperationType opType = OHOperationType.BATCH; + execute(new OperationExecuteCallback(opType, actions.size()) { + @Override + public Void execute() throws IOException { + innerBatchImpl(actions, results, opType); + return null; + } + }); + } + + private void innerBatchImpl(final List actions, final Object[] results, final OHOperationType opType) throws IOException { if (actions == null || actions.isEmpty()) { return; } @@ -848,17 +940,24 @@ private String getTargetTableName(List actions) { public void batchCallback(List actions, Object[] results, Batch.Callback callback) throws IOException, InterruptedException { - try { - batch(actions, results); - } finally { - if (results != null) { - for (int i = 0; i < results.length; i++) { - if (!(results[i] instanceof ObTableException)) { - callback.update(new byte[0], actions.get(i).getRow(), (R) results[i]); + OHOperationType opType = OHOperationType.BATCH_CALLBACK; + execute(new OperationExecuteCallback(opType, actions.size()) { + @Override + public Void execute() throws IOException { + try { + innerBatchImpl(actions, results, opType); + return null; + } finally { + if (results != null) { + for (int i = 0; i < results.length; i++) { + if (!(results[i] instanceof ObTableException)) { + callback.update(new byte[0], actions.get(i).getRow(), (R) results[i]); + } + } } } } - } + }); } public static int compareByteArray(byte[] bt1, byte[] bt2) { @@ -944,6 +1043,15 @@ private void processColumnFilters(NavigableSet columnFilters, @Override public Result get(final Get get) throws IOException { + return execute(new OperationExecuteCallback(OHOperationType.GET, 1) { + @Override + Result execute() throws IOException { + return innerGetImpl(get); + } + }); + } + + private Result innerGetImpl(final Get get) throws IOException { if (get.getFamilyMap().keySet().isEmpty()) { if (!FeatureSupport.isEmptyFamilySupported()) { throw new FeatureNotSupportedException("empty family get not supported yet within observer version: " + ObGlobal.obVsnString()); @@ -954,7 +1062,7 @@ public Result get(final Get get) throws IOException { } ServerCallable serverCallable = new ServerCallable(configuration, - obTableClient, tableNameString, get.getRow(), get.getRow(), operationTimeout) { + obTableClient, tableNameString, get.getRow(), get.getRow(), operationTimeout) { public Result call() throws IOException { List keyValueList = new ArrayList<>(); byte[] family = new byte[] {}; @@ -972,14 +1080,14 @@ public Result call() throws IOException { processColumnFilters(columnFilters, get.getFamilyMap()); obTableQuery = buildObTableQuery(get, columnFilters); ObTableQueryAsyncRequest request = buildObTableQueryAsyncRequest(obTableQuery, - getTargetTableName(tableNameString)); + getTargetTableName(tableNameString)); ObTableClientQueryAsyncStreamResult clientQueryStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient - .execute(request); + .execute(request); getMaxRowFromResult(clientQueryStreamResult, keyValueList, true, family); } else { for (Map.Entry> entry : get.getFamilyMap() - .entrySet()) { + .entrySet()) { family = entry.getKey(); if (!get.getColumnFamilyTimeRange().isEmpty()) { Map colFamTimeRangeMap = get.getColumnFamilyTimeRange(); @@ -994,19 +1102,19 @@ public Result call() throws IOException { } obTableQuery = buildObTableQuery(get, entry.getValue()); ObTableQueryRequest request = buildObTableQueryRequest(obTableQuery, - getTargetTableName(tableNameString, Bytes.toString(family), - configuration)); + getTargetTableName(tableNameString, Bytes.toString(family), + configuration)); ObTableClientQueryStreamResult clientQueryStreamResult = (ObTableClientQueryStreamResult) obTableClient - .execute(request); + .execute(request); getMaxRowFromResult(clientQueryStreamResult, keyValueList, false, - family); + family); } } } catch (Exception e) { logger.error(LCD.convert("01-00002"), tableNameString, Bytes.toString(family), - e); + e); throw new IOException("query table:" + tableNameString + " family " - + Bytes.toString(family) + " error.", e); + + Bytes.toString(family) + " error.", e); } if (get.isCheckExistenceOnly()) { return Result.create(null, !keyValueList.isEmpty()); @@ -1021,15 +1129,21 @@ public Result call() throws IOException { @Override public Result[] get(List gets) throws IOException { - Result[] results = new Result[gets.size()]; - if (ObGlobal.isHBaseBatchGetSupport()) { // get only supported in BatchSupport version - batch(gets, results); - } else { - for (int i = 0; i < gets.size(); i++) { - results[i] = get(gets.get(i)); + OHOperationType opType = OHOperationType.GET_LIST; + return execute(new OperationExecuteCallback(opType, gets.size()) { + @Override + Result[] execute() throws IOException { + Result[] results = new Result[gets.size()]; + if (ObGlobal.isHBaseBatchGetSupport()) { // get only supported in BatchSupport version + innerBatchImpl(gets, results, opType); + } else { + for (int i = 0; i < gets.size(); i++) { + results[i] = innerGetImpl(gets.get(i)); + } + } + return results; } - } - return results; + }); } @Override @@ -1234,15 +1348,29 @@ public ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOExcept @Override public void put(Put put) throws IOException { - doPut(Collections.singletonList(put)); + OHOperationType opType = OHOperationType.PUT; + execute(new OperationExecuteCallback(opType, 1) { + @Override + public Void execute() throws IOException { + doPut(Collections.singletonList(put), opType); + return null; + } + }); } @Override public void put(List puts) throws IOException { - doPut(puts); + OHOperationType opType = OHOperationType.PUT_LIST; + execute(new OperationExecuteCallback(opType, puts.size()) { + @Override + public Void execute() throws IOException { + doPut(puts, opType); + return null; + } + }); } - private void doPut(List puts) throws IOException { + private void doPut(List puts, OHOperationType opType) throws IOException { int n = 0; for (Put put : puts) { validatePut(put); @@ -1254,17 +1382,17 @@ private void doPut(List puts) throws IOException { n++; if (n % putWriteBufferCheck == 0 && currentWriteBufferSize.get() > writeBufferSize) { if (OHBaseFuncUtils.isHBasePutPefSupport(obTableClient)) { - flushCommitsV2(); + flushCommitsV2(opType); } else { - flushCommits(); + flushCommits(opType); } } } if (autoFlush || currentWriteBufferSize.get() > writeBufferSize) { if (OHBaseFuncUtils.isHBasePutPefSupport(obTableClient)) { - flushCommitsV2(); + flushCommitsV2(opType); } else { - flushCommits(); + flushCommits(opType); } } } @@ -1355,12 +1483,10 @@ public boolean checkAndPut(final byte[] row, final byte[] family, final byte[] q return checkAndPut(row, family, qualifier, getCompareOp(op), value, put); } - private void innerDelete(Delete delete) throws IOException { - checkArgument(delete.getRow() != null, "row is null"); + private void innerDelete(List deletes, OHOperationType opType) throws IOException { try { - List actions = Collections.singletonList(delete); - Object[] results = new Object[actions.size()]; - batch(actions, results); + Object[] results = new Object[deletes.size()]; + innerBatchImpl(deletes, results, opType); } catch (Exception e) { logger.error(LCD.convert("01-00004"), tableNameString, e); throw e; @@ -1369,15 +1495,27 @@ private void innerDelete(Delete delete) throws IOException { @Override public void delete(Delete delete) throws IOException { - checkFamilyViolation(delete.getFamilyCellMap().keySet(), false); - innerDelete(delete); + OHOperationType opType = OHOperationType.DELETE; + execute(new OperationExecuteCallback(opType, 1) { + @Override + public Void execute() throws IOException { + checkFamilyViolation(delete.getFamilyCellMap().keySet(), false); + innerDelete(Collections.singletonList(delete), opType); + return null; + } + }); } @Override public void delete(List deletes) throws IOException { - for (Delete delete : deletes) { - innerDelete(delete); - } + OHOperationType opType = OHOperationType.DELETE_LIST; + execute(new OperationExecuteCallback(opType, deletes.size()) { + @Override + public Void execute() throws IOException { + innerDelete(deletes, opType); + return null; + } + }); } /** @@ -1636,7 +1774,7 @@ public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, lo return incrementColumnValue(row, family, qualifier, amount); } - public void flushCommits() throws IOException { + public void flushCommits(OHOperationType opType) throws IOException { try { if (writeBuffer.isEmpty()) { @@ -1646,7 +1784,7 @@ public void flushCommits() throws IOException { boolean[] resultSuccess = new boolean[writeBuffer.size()]; try { Object[] results = new Object[writeBuffer.size()]; - batch(writeBuffer, results); + innerBatchImpl(writeBuffer, results, opType); for (int i = 0; i != resultSuccess.length; ++i) { if (results[i] instanceof ObTableException) { resultSuccess[i] = false; @@ -1693,13 +1831,13 @@ public void flushCommits() throws IOException { } } - public void flushCommitsV2() throws IOException { + public void flushCommitsV2(OHOperationType opType) throws IOException { try { if (writeBuffer.isEmpty()) { return; } try { - ObHbaseRequest request = buildHbaseRequest(writeBuffer); + ObHbaseRequest request = buildHbaseRequest(writeBuffer, opType); try { ObHbaseResult result = (ObHbaseResult) obTableClient.execute(request); } catch (Exception e) { @@ -2324,6 +2462,7 @@ private BatchOperation buildBatchOperation(String tableName, List } else if (row instanceof Delete) { boolean disExec = obTableClient.getServerCapacity().isSupportDistributedExecute(); Delete delete = (Delete) row; + checkArgument(delete.getRow() != null, "row is null"); if (delete.isEmpty()) { singleOpResultNum++; if (disExec) { diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java new file mode 100644 index 00000000..fcd0c95a --- /dev/null +++ b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java @@ -0,0 +1,105 @@ +package com.alipay.oceanbase.hbase.util; + +import com.codahale.metrics.Snapshot; +import com.codahale.metrics.Timer; + +public class MetricsExporter { + private double failRate; + private double averageSingleOpCount; + private Timer latencyHistogram; // for p99 + private Snapshot latencySnapshot; + + public MetricsExporter() { + this.failRate = 0; + this.averageSingleOpCount = 0; + this.latencyHistogram = null; + this.latencySnapshot = null; + } + + public void setFailRate(double failRate) { + this.failRate = failRate; + } + + public void setAverageSingleOpCount(double averageSingleOpCount) { + this.averageSingleOpCount = averageSingleOpCount; + } + + public void setLatencyHistogram(Timer latencyHistogram) { + this.latencyHistogram = latencyHistogram; + } + + public void setLatencySnapshot(Snapshot latencySnapshot) { + this.latencySnapshot = latencySnapshot; + } + + public double getAverageOps() { + return latencyHistogram.getMeanRate(); + } + + public double getOneMinuteAverageOps() { + return latencyHistogram.getOneMinuteRate(); + } + + public double getFiveMinuteAverageOps() { + return latencyHistogram.getFiveMinuteRate(); + } + + public double getFifteenMinuteAverageOps() { + return latencyHistogram.getFifteenMinuteRate(); + } + + public double getFailRate() { + return failRate; + } + + public double getAverageSingleOpCount() { + return averageSingleOpCount; + } + + public double getAverageLatency() { + return latencySnapshot.getMean(); + } + + public long getMaxLatency() { + return latencySnapshot.getMax(); + } + + public long getMinLatency() { + return latencySnapshot.getMin(); + } + + public double getMedian() { + return latencySnapshot.getMedian(); + } + + public double get75thPercentile() { + return latencySnapshot.get75thPercentile(); + } + + public double get95thPercentile() { + return latencySnapshot.get95thPercentile(); + } + + public double get98thPercentile() { + return latencySnapshot.get98thPercentile(); + } + + public double get99thPercentile() { + return latencySnapshot.get99thPercentile(); + } + + public double get999thPercentile() { + return latencySnapshot.get999thPercentile(); + } + + public static MetricsExporter getInstanceOf(double averageSingleOpCount, + double failRate, + Timer latencyHistogram) { + MetricsExporter exporter = new MetricsExporter(); + exporter.setAverageSingleOpCount(averageSingleOpCount); + exporter.setFailRate(failRate); + exporter.setLatencyHistogram(latencyHistogram); + exporter.setLatencySnapshot(latencyHistogram.getSnapshot()); + return exporter; + } +} diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java new file mode 100644 index 00000000..e2c6ff17 --- /dev/null +++ b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java @@ -0,0 +1,37 @@ +package com.alipay.oceanbase.hbase.util; + +public class MetricsImporter { + private boolean isFailedOp; + private long duration; + private long singleOpCount; + + public MetricsImporter() { + this.isFailedOp = false; + this.duration = 0; + this.singleOpCount = 0; + } + + public void setIsFailedOp(boolean isFailedOp) { + this.isFailedOp = isFailedOp; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public void setSingleOpCount(long singleOpCount) { + this.singleOpCount = singleOpCount; + } + + public boolean isFailedOp() { + return isFailedOp; + } + + public long getDuration() { + return duration; + } + + public long getSingleOpCount() { + return singleOpCount; + } +} diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHBaseFuncUtils.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHBaseFuncUtils.java index 61b4ef65..1a0ecd05 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHBaseFuncUtils.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHBaseFuncUtils.java @@ -122,4 +122,20 @@ public static boolean needTabletId(ObTableClient tableClient) { && tableClient.getServerCapacity().isSupportDistributedExecute(); } } + + // names concatenated by periods + public static String metricsNameBuilder(String... name) { + StringBuilder builder = new StringBuilder(); + if (name != null) { + for (String n : name) { + if (n != null && !n.isEmpty()) { + if (builder.length() > 0) { + builder.append('.'); + } + builder.append(n); + } + } + } + return builder.toString(); + } } diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java new file mode 100644 index 00000000..02fd8df7 --- /dev/null +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java @@ -0,0 +1,73 @@ +package com.alipay.oceanbase.hbase.util; + +import com.alipay.oceanbase.rpc.location.model.partition.ObPair; +import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; +import com.codahale.metrics.JmxReporter; +import com.codahale.metrics.MetricRegistry; +import org.slf4j.Logger; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class OHMetrics { + private static final Logger logger = TableHBaseLoggerFactory.getLogger(OHMetrics.class); + private final String metricsName; + private final MetricRegistry registry; + private final JmxReporter reporter; + private static OHMetricsTracker[] trackers; + private final ConcurrentLinkedQueue> metricsQueue = new ConcurrentLinkedQueue<>(); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + public OHMetrics(String metricsName) { + this.metricsName = metricsName; + this.registry = new MetricRegistry(); + trackers = new OHMetricsTracker[OHOperationType.values().length]; + for (int i = 0; i < trackers.length; ++i) { + OHOperationType opType = OHOperationType.valueOf(i); + trackers[i] = new OHMetricsTracker(this.registry, + metricsName, + opType); + } + this.reporter = JmxReporter.forRegistry(this.registry).build(); + this.reporter.start(); + } + public void start() { + scheduler.scheduleWithFixedDelay(this::updateMetrics, 10, 0, TimeUnit.SECONDS); + } + // get the size of current queue,only update these metrics to corresponding trackers, ignore those concurrently added metrics + private void updateMetrics() { + try { + long size = metricsQueue.size(); + for (long i = 0; i < size; ++i) { + ObPair pair = null; + if ((pair = metricsQueue.poll()) != null) { + OHMetricsTracker tracker = getTracker(pair.getLeft()); + MetricsImporter importer = pair.getRight(); + tracker.update(importer); + } else { + break; + } + } + } catch (Exception e) { + logger.warn("update metrics meets exception", e); + } + } + + private OHMetricsTracker getTracker(OHOperationType opType) { + return trackers[opType.getValue()]; + } + + public String getMetricsName() { return this.metricsName; } + + // add metrics into queue asynchronously + public void update(ObPair importerPair) { + metricsQueue.add(importerPair); + } + // OPS,RT,P99,failures + public MetricsExporter acquireMetrics(OHOperationType opType) { + OHMetricsTracker tracker = getTracker(opType); + return tracker.acquireMetrics(); + } +} diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java new file mode 100644 index 00000000..73a0726c --- /dev/null +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java @@ -0,0 +1,53 @@ +package com.alipay.oceanbase.hbase.util; + +import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; +import com.codahale.metrics.*; + +import java.util.concurrent.TimeUnit; + +import static com.codahale.metrics.MetricRegistry.name; + +public class OHMetricsTracker { + private final OHOperationType opType; + private final Timer latencyHistogram; + private final Meter failedOpCounter; + private final Counter totalSingleOpCount; // the number of single mutations or queries + private final Counter totalRuntime; + + public OHMetricsTracker(MetricRegistry registry, String metricsName, OHOperationType opType) { + String typeName = opType.name(); + this.opType = opType; + this.latencyHistogram = registry.timer( + name(OHMetrics.class, typeName, "latencyHistogram", metricsName)); + this.failedOpCounter = registry.meter( + name(OHMetrics.class, typeName, "failedOpCounter", metricsName)); + this.totalSingleOpCount = registry.counter( + name(OHMetrics.class, typeName, "totalSingleOpCount", metricsName)); + this.totalRuntime = registry.counter( + name(OHMetrics.class, typeName, "totalRuntime", metricsName)); + } + + public OHOperationType getOpType() { + return this.opType; + } + + public void update(MetricsImporter importer) { + latencyHistogram.update(importer.getDuration(), TimeUnit.MILLISECONDS); + if (importer.isFailedOp()) { + failedOpCounter.mark(); + } + totalSingleOpCount.inc(importer.getSingleOpCount()); + totalRuntime.inc(importer.getDuration()); + } + + public MetricsExporter acquireMetrics() { + long curTotalCount = this.latencyHistogram.getCount(); + long curSingleOpCount = this.totalSingleOpCount.getCount(); + double averageSingleOpCount = ((double) curSingleOpCount) / ((double) curTotalCount); // the average number of single op per request + double failRate = this.failedOpCounter.getFiveMinuteRate(); // fail rate in 15 minutes + + return MetricsExporter.getInstanceOf(averageSingleOpCount, + failRate, + latencyHistogram); + } +} From 448751fcd351ef475738d6ecae0ceb5463f4ca8f Mon Sep 17 00:00:00 2001 From: JackShi148 Date: Fri, 24 Oct 2025 17:36:30 +0800 Subject: [PATCH 02/15] add hbaseOpType into request --- .../com/alipay/oceanbase/hbase/OHTable.java | 599 +++++++++--------- .../hbase/result/ClientStreamScanner.java | 21 +- .../oceanbase/hbase/util/OHBaseFuncUtils.java | 18 +- .../oceanbase/hbase/util/OHMetrics.java | 9 +- 4 files changed, 347 insertions(+), 300 deletions(-) diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index 549c09dc..480d9d50 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -199,7 +199,7 @@ public class OHTable implements Table { private RegionLocator regionLocator; - private final OHMetrics metrics; + private final OHMetrics metrics; /** * Creates an object to access a HBase table. @@ -552,7 +552,7 @@ private T execute(OperationExecuteCallback callback) throws IOException { // do not deal with any exception, just record importer.setIsFailedOp(true); // set as failed op if (e instanceof IOException) { - throw e; + throw (IOException) e; } else { throw new IOException("meet non-IOException in callback execute", e); } @@ -603,33 +603,47 @@ public TableDescriptor getDescriptor() throws IOException { */ @Override public boolean exists(Get get) throws IOException { - return execute(new OperationExecuteCallback(OHOperationType.EXISTS, 1) { + OHOperationType opType = OHOperationType.EXISTS; + return execute(new OperationExecuteCallback(opType, 1) { @Override Boolean execute() throws IOException { Get newGet = new Get(get); newGet.setCheckExistenceOnly(true); - return innerGetImpl(newGet).getExists(); + return innerGetImpl(newGet, opType).getExists(); } }); } @Override public boolean[] existsAll(List gets) throws IOException { - boolean[] ret = new boolean[gets.size()]; - List newGets = new ArrayList<>(); - // if just checkExistOnly, batch get will not return any result or row count - // therefore we have to set checkExistOnly as false and so the result can be returned - // TODO: adjust ExistOnly in server when using batch get - for (Get get : gets) { - Get newGet = new Get(get); - newGet.setCheckExistenceOnly(false); - newGets.add(newGet); - } - Result[] results = get(newGets); - for (int i = 0; i < results.length; ++i) { - ret[i] = !results[i].isEmpty(); - } - return ret; + OHOperationType opType = OHOperationType.EXISTS_LIST; + return execute(new OperationExecuteCallback(opType, gets.size()) { + @Override + boolean[] execute() throws IOException { + boolean[] ret = new boolean[gets.size()]; + List newGets = new ArrayList<>(); + // if just checkExistOnly, batch get will not return any result or row count + // therefore we have to set checkExistOnly as false and so the result can be returned + // TODO: adjust ExistOnly in server when using batch get + for (Get get : gets) { + Get newGet = new Get(get); + newGet.setCheckExistenceOnly(false); + newGets.add(newGet); + } + Result[] results = new Result[newGets.size()]; + if (ObGlobal.isHBaseBatchGetSupport()) { // get only supported in BatchSupport version + innerBatchImpl(newGets, results, opType); + } else { + for (int i = 0; i < newGets.size(); i++) { + results[i] = innerGetImpl(newGets.get(i), opType); // TODO:循环执行的类型用什么? + } + } + for (int i = 0; i < results.length; ++i) { + ret[i] = !results[i].isEmpty(); + } + return ret; + } + }); } @Override @@ -814,9 +828,9 @@ private void innerBatchImpl(final List actions, final Object[] re } catch (Exception e) { throw new IOException(tableNameString + " table occurred unexpected error." , e); } - } else if (OHBaseFuncUtils.isAllPut(actions) && OHBaseFuncUtils.isHBasePutPefSupport(obTableClient)) { + } else if (OHBaseFuncUtils.isAllPut(opType, actions) && OHBaseFuncUtils.isHBasePutPefSupport(obTableClient)) { // only support Put now - ObHbaseRequest request = buildHbaseRequest(actions); + ObHbaseRequest request = buildHbaseRequest(actions, opType); try { ObHbaseResult result = (ObHbaseResult) obTableClient.execute(request); if (results != null) { @@ -831,6 +845,7 @@ private void innerBatchImpl(final List actions, final Object[] re String realTableName = getTargetTableName(actions); BatchOperation batch = buildBatchOperation(realTableName, actions, tableNameString.equals(realTableName), resultMapSingleOp); + batch.setHbaseOpType(opType); BatchOperationResult tmpResults; try { tmpResults = batch.execute(); @@ -1043,15 +1058,16 @@ private void processColumnFilters(NavigableSet columnFilters, @Override public Result get(final Get get) throws IOException { - return execute(new OperationExecuteCallback(OHOperationType.GET, 1) { + OHOperationType opType = OHOperationType.GET; + return execute(new OperationExecuteCallback(opType, 1) { @Override Result execute() throws IOException { - return innerGetImpl(get); + return innerGetImpl(get, opType); } }); } - private Result innerGetImpl(final Get get) throws IOException { + private Result innerGetImpl(final Get get, OHOperationType opType) throws IOException { if (get.getFamilyMap().keySet().isEmpty()) { if (!FeatureSupport.isEmptyFamilySupported()) { throw new FeatureNotSupportedException("empty family get not supported yet within observer version: " + ObGlobal.obVsnString()); @@ -1080,7 +1096,7 @@ public Result call() throws IOException { processColumnFilters(columnFilters, get.getFamilyMap()); obTableQuery = buildObTableQuery(get, columnFilters); ObTableQueryAsyncRequest request = buildObTableQueryAsyncRequest(obTableQuery, - getTargetTableName(tableNameString)); + getTargetTableName(tableNameString), opType); ObTableClientQueryAsyncStreamResult clientQueryStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient .execute(request); @@ -1103,7 +1119,7 @@ public Result call() throws IOException { obTableQuery = buildObTableQuery(get, entry.getValue()); ObTableQueryRequest request = buildObTableQueryRequest(obTableQuery, getTargetTableName(tableNameString, Bytes.toString(family), - configuration)); + configuration), opType); ObTableClientQueryStreamResult clientQueryStreamResult = (ObTableClientQueryStreamResult) obTableClient .execute(request); getMaxRowFromResult(clientQueryStreamResult, keyValueList, false, @@ -1138,7 +1154,7 @@ Result[] execute() throws IOException { innerBatchImpl(gets, results, opType); } else { for (int i = 0; i < gets.size(); i++) { - results[i] = innerGetImpl(gets.get(i)); + results[i] = innerGetImpl(gets.get(i), opType); // TODO: 这种单次循环执行的类型是用 LIST 类型还是用 EXIST 类型? } } return results; @@ -1148,86 +1164,93 @@ Result[] execute() throws IOException { @Override public ResultScanner getScanner(final Scan scan) throws IOException { - if (scan.getFamilyMap().keySet().isEmpty()) { - if (!FeatureSupport.isEmptyFamilySupported()) { - throw new FeatureNotSupportedException("empty family scan not supported yet within observer version: " + ObGlobal.obVsnString()); - } - // check nothing, use table group; - } else { - checkFamilyViolation(scan.getFamilyMap().keySet(), false); - } - - //be careful about the packet size ,may the packet exceed the max result size ,leading to error - ServerCallable serverCallable = new ServerCallable( - configuration, obTableClient, tableNameString, scan.getStartRow(), scan.getStopRow(), - operationTimeout) { - public ResultScanner call() throws IOException { - byte[] family = new byte[] {}; - ObTableClientQueryAsyncStreamResult clientQueryAsyncStreamResult; - ObTableQueryAsyncRequest request; - ObTableQuery obTableQuery; - ObHTableFilter filter; - try { - if (scan.getFamilyMap().keySet().isEmpty() - || scan.getFamilyMap().size() > 1) { - // In a Scan operation where the family map is greater than 1 or equal to 0, - // we handle this by appending the column family to the qualifier on the client side. - // The server can then use this information to filter the appropriate column families and qualifiers. - if (!scan.getColumnFamilyTimeRange().isEmpty()) { - throw new FeatureNotSupportedException("setColumnFamilyTimeRange is only supported in single column family for now"); - } - NavigableSet columnFilters = new TreeSet<>(Bytes.BYTES_COMPARATOR); - processColumnFilters(columnFilters, scan.getFamilyMap()); - filter = buildObHTableFilter(scan.getFilter(), scan.getTimeRange(), - scan.getMaxVersions(), columnFilters); - obTableQuery = buildObTableQuery(filter, scan); + return execute(new OperationExecuteCallback(OHOperationType.SCAN, 1) { + @Override + ResultScanner execute() throws IOException { + if (scan.getFamilyMap().keySet().isEmpty()) { + if (!FeatureSupport.isEmptyFamilySupported()) { + throw new FeatureNotSupportedException("empty family scan not supported yet within observer version: " + ObGlobal.obVsnString()); + } + // check nothing, use table group; + } else { + checkFamilyViolation(scan.getFamilyMap().keySet(), false); + } - request = buildObTableQueryAsyncRequest(obTableQuery, - getTargetTableName(tableNameString)); - clientQueryAsyncStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient - .execute(request); - return new ClientStreamScanner(clientQueryAsyncStreamResult, - tableNameString, scan, true); - } else { - for (Map.Entry> entry : scan.getFamilyMap() - .entrySet()) { - family = entry.getKey(); - if (!scan.getColumnFamilyTimeRange().isEmpty()) { - Map colFamTimeRangeMap = scan.getColumnFamilyTimeRange(); - if (colFamTimeRangeMap.size() > 1) { + //be careful about the packet size ,may the packet exceed the max result size ,leading to error + ServerCallable serverCallable = new ServerCallable( + configuration, obTableClient, tableNameString, scan.getStartRow(), scan.getStopRow(), + operationTimeout) { + public ResultScanner call() throws IOException { + byte[] family = new byte[] {}; + ObTableClientQueryAsyncStreamResult clientQueryAsyncStreamResult; + ObTableQueryAsyncRequest request; + ObTableQuery obTableQuery; + ObHTableFilter filter; + try { + if (scan.getFamilyMap().keySet().isEmpty() + || scan.getFamilyMap().size() > 1) { + // In a Scan operation where the family map is greater than 1 or equal to 0, + // we handle this by appending the column family to the qualifier on the client side. + // The server can then use this information to filter the appropriate column families and qualifiers. + if (!scan.getColumnFamilyTimeRange().isEmpty()) { throw new FeatureNotSupportedException("setColumnFamilyTimeRange is only supported in single column family for now"); - } else if (colFamTimeRangeMap.get(family) == null) { - throw new IllegalArgumentException("Scan family is not matched in ColumnFamilyTimeRange"); - } else { - TimeRange tr = colFamTimeRangeMap.get(family); - scan.setTimeRange(tr.getMin(), tr.getMax()); + } + NavigableSet columnFilters = new TreeSet<>(Bytes.BYTES_COMPARATOR); + processColumnFilters(columnFilters, scan.getFamilyMap()); + filter = buildObHTableFilter(scan.getFilter(), scan.getTimeRange(), + scan.getMaxVersions(), columnFilters); + obTableQuery = buildObTableQuery(filter, scan); + + request = buildObTableQueryAsyncRequest(obTableQuery, + getTargetTableName(tableNameString), + OHOperationType.SCAN); + clientQueryAsyncStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient + .execute(request); + return new ClientStreamScanner(clientQueryAsyncStreamResult, + tableNameString, scan, true, metrics); + } else { + for (Map.Entry> entry : scan.getFamilyMap() + .entrySet()) { + family = entry.getKey(); + if (!scan.getColumnFamilyTimeRange().isEmpty()) { + Map colFamTimeRangeMap = scan.getColumnFamilyTimeRange(); + if (colFamTimeRangeMap.size() > 1) { + throw new FeatureNotSupportedException("setColumnFamilyTimeRange is only supported in single column family for now"); + } else if (colFamTimeRangeMap.get(family) == null) { + throw new IllegalArgumentException("Scan family is not matched in ColumnFamilyTimeRange"); + } else { + TimeRange tr = colFamTimeRangeMap.get(family); + scan.setTimeRange(tr.getMin(), tr.getMax()); + } + } + filter = buildObHTableFilter(scan.getFilter(), scan.getTimeRange(), + scan.getMaxVersions(), entry.getValue()); + obTableQuery = buildObTableQuery(filter, scan); + + request = buildObTableQueryAsyncRequest( + obTableQuery, + getTargetTableName(tableNameString, Bytes.toString(family), + configuration), + OHOperationType.SCAN); + clientQueryAsyncStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient + .execute(request); + return new ClientStreamScanner(clientQueryAsyncStreamResult, + tableNameString, scan, false, metrics); } } - filter = buildObHTableFilter(scan.getFilter(), scan.getTimeRange(), - scan.getMaxVersions(), entry.getValue()); - obTableQuery = buildObTableQuery(filter, scan); - - request = buildObTableQueryAsyncRequest( - obTableQuery, - getTargetTableName(tableNameString, Bytes.toString(family), - configuration)); - clientQueryAsyncStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient - .execute(request); - return new ClientStreamScanner(clientQueryAsyncStreamResult, - tableNameString, scan, false); + } catch (Exception e) { + logger.error(LCD.convert("01-00003"), tableNameString, Bytes.toString(family), + e); + throw new IOException("scan table:" + tableNameString + " family " + + Bytes.toString(family) + " error.", e); } - } - } catch (Exception e) { - logger.error(LCD.convert("01-00003"), tableNameString, Bytes.toString(family), - e); - throw new IOException("scan table:" + tableNameString + " family " - + Bytes.toString(family) + " error.", e); - } - throw new IOException("scan table:" + tableNameString + "has no family"); + throw new IOException("scan table:" + tableNameString + "has no family"); + } + }; + return executeServerCallable(serverCallable); } - }; - return executeServerCallable(serverCallable); + }); } public List getScanners(final Scan scan) throws IOException { @@ -1265,7 +1288,8 @@ public List call() throws IOException { obTableQuery = buildObTableQuery(filter, scan); request = buildObTableQueryAsyncRequest(obTableQuery, - getTargetTableName(tableNameString)); + getTargetTableName(tableNameString), + OHOperationType.SCAN); request.setNeedTabletId(false); request.setAllowDistributeScan(false); String phyTableName = obTableClient.getPhyTableNameFromTableGroup( @@ -1277,7 +1301,7 @@ public List call() throws IOException { clientQueryAsyncStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient .execute(request); ClientStreamScanner clientScanner = new ClientStreamScanner( - clientQueryAsyncStreamResult, tableNameString, scan, true); + clientQueryAsyncStreamResult, tableNameString, scan, true, metrics); resultScanners.add(clientScanner); } return resultScanners; @@ -1302,7 +1326,7 @@ public List call() throws IOException { String targetTableName = getTargetTableName(tableNameString, Bytes.toString(family), configuration); - request = buildObTableQueryAsyncRequest(obTableQuery, targetTableName); + request = buildObTableQueryAsyncRequest(obTableQuery, targetTableName, OHOperationType.SCAN); request.setNeedTabletId(false); request.setAllowDistributeScan(false); List partitions = obTableClient @@ -1313,7 +1337,7 @@ public List call() throws IOException { clientQueryAsyncStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient .execute(request); ClientStreamScanner clientScanner = new ClientStreamScanner( - clientQueryAsyncStreamResult, tableNameString, scan, false); + clientQueryAsyncStreamResult, tableNameString, scan, false, metrics); resultScanners.add(clientScanner); } return resultScanners; @@ -1467,13 +1491,7 @@ public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, throws IOException { RowMutations rowMutations = new RowMutations(row); rowMutations.add(put); - try { - return checkAndMutation(row, family, qualifier, compareOp, value, null, rowMutations); - } catch (Exception e) { - logger.error(LCD.convert("01-00005"), put, tableNameString, e); - throw new IOException("checkAndPut type table:" + tableNameString + " e.msg:" - + e.getMessage() + " error.", e); - } + return checkAndMutation(row, family, qualifier, compareOp, value, null, rowMutations, OHOperationType.CHECK_AND_PUT); } @Override @@ -1540,13 +1558,8 @@ public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, throws IOException { RowMutations rowMutations = new RowMutations(row); rowMutations.add(delete); - try { - return checkAndMutation(row, family, qualifier, compareOp, value, null, rowMutations); - } catch (Exception e) { - logger.error(LCD.convert("01-00005"), delete, tableNameString, e); - throw new IOException("checkAndDelete type table:" + tableNameString + " e.msg:" - + e.getMessage() + " error.", e); - } + return checkAndMutation(row, family, qualifier, compareOp, value, null, rowMutations, OHOperationType.CHECK_AND_DELETE); + } @Override @@ -1560,13 +1573,7 @@ public boolean checkAndDelete(final byte[] row, final byte[] family, final byte[ public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier, CompareFilter.CompareOp compareOp, byte[] value, RowMutations rowMutations) throws IOException { - try { - return checkAndMutation(row, family, qualifier, compareOp, value, null, rowMutations); - } catch (Exception e) { - logger.error(LCD.convert("01-00005"), rowMutations, tableNameString, e); - throw new IOException("checkAndMutate type table:" + tableNameString + " e.msg:" - + e.getMessage() + " error.", e); - } + return checkAndMutation(row, family, qualifier, compareOp, value, null, rowMutations, OHOperationType.CHECK_AND_MUTATE); } @Override @@ -1582,35 +1589,46 @@ public CheckAndMutateBuilder checkAndMutate(byte[] row, byte[] family) { private boolean checkAndMutation(byte[] row, byte[] family, byte[] qualifier, CompareFilter.CompareOp compareOp, byte[] value, - TimeRange timeRange, RowMutations rowMutations) - throws Exception { - checkArgument(row != null, "row is null"); - checkArgument(isNotBlank(Bytes.toString(family)), "family is blank"); - checkArgument(Bytes.equals(row, rowMutations.getRow()), - "mutation row is not equal check row"); - checkArgument(!rowMutations.getMutations().isEmpty(), "mutation is empty"); - List mutations = rowMutations.getMutations(); - // only one family operation is allowed - for (Mutation mutation : mutations) { - if (!(mutation instanceof Put || mutation instanceof Delete)) { - throw new DoNotRetryIOException("RowMutations supports only put and delete, not " - + mutation.getClass().getName()); + TimeRange timeRange, RowMutations rowMutations, OHOperationType opType) + throws IOException { + return execute(new OperationExecuteCallback(opType, rowMutations.getMutations().size()) { + @Override + Boolean execute() throws IOException { + try { + checkArgument(row != null, "row is null"); + checkArgument(isNotBlank(Bytes.toString(family)), "family is blank"); + checkArgument(Bytes.equals(row, rowMutations.getRow()), + "mutation row is not equal check row"); + checkArgument(!rowMutations.getMutations().isEmpty(), "mutation is empty"); + List mutations = rowMutations.getMutations(); + // only one family operation is allowed + for (Mutation mutation : mutations) { + if (!(mutation instanceof Put || mutation instanceof Delete)) { + throw new DoNotRetryIOException("RowMutations supports only put and delete, not " + + mutation.getClass().getName()); + } + checkFamilyViolationForOneFamily(mutation.getFamilyCellMap().keySet()); + checkArgument(Arrays.equals(family, mutation.getFamilyCellMap().firstEntry().getKey()), + "mutation family is not equal check family"); + } + byte[] filterString = buildCheckAndMutateFilterString(family, qualifier, compareOp, value); + ObHTableFilter filter = buildObHTableFilter(filterString, timeRange, 1, qualifier); + ObTableQuery obTableQuery = buildObTableQuery(filter, row, true, row, true, false, + new TimeRange()); + ObTableBatchOperation batch = buildObTableBatchOperation(mutations, null); + + ObTableQueryAndMutateRequest request = buildObTableQueryAndMutateRequest(obTableQuery, + batch, getTargetTableName(tableNameString, Bytes.toString(family), configuration), opType); + ObTableQueryAndMutateResult result = (ObTableQueryAndMutateResult) obTableClient + .execute(request); + return result.getAffectedRows() > 0; + } catch (Exception e) { + logger.error(LCD.convert("01-00005"), rowMutations, tableNameString, e); + throw new IOException(opType.name() + " type table:" + tableNameString + " e.msg:" + + e.getMessage() + " error.", e); + } } - checkFamilyViolationForOneFamily(mutation.getFamilyCellMap().keySet()); - checkArgument(Arrays.equals(family, mutation.getFamilyCellMap().firstEntry().getKey()), - "mutation family is not equal check family"); - } - byte[] filterString = buildCheckAndMutateFilterString(family, qualifier, compareOp, value); - ObHTableFilter filter = buildObHTableFilter(filterString, timeRange, 1, qualifier); - ObTableQuery obTableQuery = buildObTableQuery(filter, row, true, row, true, false, - new TimeRange()); - ObTableBatchOperation batch = buildObTableBatchOperation(mutations, null); - - ObTableQueryAndMutateRequest request = buildObTableQueryAndMutateRequest(obTableQuery, - batch, getTargetTableName(tableNameString, Bytes.toString(family), configuration)); - ObTableQueryAndMutateResult result = (ObTableQueryAndMutateResult) obTableClient - .execute(request); - return result.getAffectedRows() > 0; + }); } @Override @@ -1627,49 +1645,55 @@ public void mutateRow(RowMutations rm) { */ @Override public Result append(Append append) throws IOException { - checkArgument(!append.isEmpty(), "Invalid arguments to %s, zero columns specified", - append.toString()); - checkFamilyViolationForOneFamily(append.getFamilyCellMap().keySet()); - try { - byte[] r = append.getRow(); - Map.Entry> entry = append.getFamilyCellMap().entrySet().iterator() - .next(); - byte[] f = entry.getKey(); - List qualifiers = new ArrayList(); - ObTableBatchOperation batchOperation = buildObTableBatchOperation( - Collections.singletonList(append), qualifiers); - // the later hbase has supported timeRange - ObHTableFilter filter = buildObHTableFilter(null, null, 1, qualifiers); - ObTableQuery obTableQuery = buildObTableQuery(filter, r, true, r, true, false, - new TimeRange()); - ObTableQueryAndMutate queryAndMutate = new ObTableQueryAndMutate(); - queryAndMutate.setTableQuery(obTableQuery); - queryAndMutate.setMutations(batchOperation); - ObTableQueryAndMutateRequest request = buildObTableQueryAndMutateRequest(obTableQuery, - batchOperation, - getTargetTableName(tableNameString, Bytes.toString(f), configuration)); - request.setReturningAffectedEntity(append.isReturnResults()); - ObTableQueryAndMutateResult result = (ObTableQueryAndMutateResult) obTableClient - .execute(request); - if (!append.isReturnResults()) { - return null; - } - ObTableQueryResult queryResult = result.getAffectedEntity(); - List keyValues = new ArrayList(); - for (List row : queryResult.getPropertiesRows()) { - byte[] k = (byte[]) row.get(0).getValue(); - byte[] q = (byte[]) row.get(1).getValue(); - long t = (Long) row.get(2).getValue(); - byte[] v = (byte[]) row.get(3).getValue(); - KeyValue kv = new KeyValue(k, f, q, t, v); - - keyValues.add(kv); + OHOperationType opType = OHOperationType.APPEND; + return execute(new OperationExecuteCallback(opType, 1) { + @Override + Result execute() throws IOException { + checkArgument(!append.isEmpty(), "Invalid arguments to %s, zero columns specified", + append.toString()); + checkFamilyViolationForOneFamily(append.getFamilyCellMap().keySet()); + try { + byte[] r = append.getRow(); + Map.Entry> entry = append.getFamilyCellMap().entrySet().iterator() + .next(); + byte[] f = entry.getKey(); + List qualifiers = new ArrayList(); + ObTableBatchOperation batchOperation = buildObTableBatchOperation( + Collections.singletonList(append), qualifiers); + // the later hbase has supported timeRange + ObHTableFilter filter = buildObHTableFilter(null, null, 1, qualifiers); + ObTableQuery obTableQuery = buildObTableQuery(filter, r, true, r, true, false, + new TimeRange()); + ObTableQueryAndMutate queryAndMutate = new ObTableQueryAndMutate(); + queryAndMutate.setTableQuery(obTableQuery); + queryAndMutate.setMutations(batchOperation); + ObTableQueryAndMutateRequest request = buildObTableQueryAndMutateRequest(obTableQuery, + batchOperation, + getTargetTableName(tableNameString, Bytes.toString(f), configuration), opType); + request.setReturningAffectedEntity(append.isReturnResults()); + ObTableQueryAndMutateResult result = (ObTableQueryAndMutateResult) obTableClient + .execute(request); + if (!append.isReturnResults()) { + return null; + } + ObTableQueryResult queryResult = result.getAffectedEntity(); + List keyValues = new ArrayList(); + for (List row : queryResult.getPropertiesRows()) { + byte[] k = (byte[]) row.get(0).getValue(); + byte[] q = (byte[]) row.get(1).getValue(); + long t = (Long) row.get(2).getValue(); + byte[] v = (byte[]) row.get(3).getValue(); + KeyValue kv = new KeyValue(k, f, q, t, v); + + keyValues.add(kv); + } + return Result.create(keyValues); + } catch (Exception e) { + logger.error(LCD.convert("01-00006"), tableNameString, e); + throw new IOException("append table " + tableNameString + " error.", e); + } } - return Result.create(keyValues); - } catch (Exception e) { - logger.error(LCD.convert("01-00006"), tableNameString, e); - throw new IOException("append table " + tableNameString + " error.", e); - } + }); } /** @@ -1681,46 +1705,52 @@ public Result append(Append append) throws IOException { */ @Override public Result increment(Increment increment) throws IOException { - checkArgument(!increment.isEmpty(), "Invalid arguments to %s, zero columns specified", increment.toString()); - checkFamilyViolationForOneFamily(increment.getFamilyCellMap().keySet()); + OHOperationType opType = OHOperationType.INCREMENT; + return execute(new OperationExecuteCallback(opType, 1) { + @Override + Result execute() throws IOException { + checkArgument(!increment.isEmpty(), "Invalid arguments to %s, zero columns specified", increment.toString()); + checkFamilyViolationForOneFamily(increment.getFamilyCellMap().keySet()); - try { - byte[] rowKey = increment.getRow(); - Map.Entry> entry = increment.getFamilyCellMap().entrySet() - .iterator().next(); + try { + byte[] rowKey = increment.getRow(); + Map.Entry> entry = increment.getFamilyCellMap().entrySet() + .iterator().next(); - byte[] f = entry.getKey(); - List qualifiers = new ArrayList<>(); - ObTableBatchOperation batch = buildObTableBatchOperation(Collections.singletonList(increment), qualifiers); + byte[] f = entry.getKey(); + List qualifiers = new ArrayList<>(); + ObTableBatchOperation batch = buildObTableBatchOperation(Collections.singletonList(increment), qualifiers); - ObHTableFilter filter = buildObHTableFilter(null, increment.getTimeRange(), 1, - qualifiers); + ObHTableFilter filter = buildObHTableFilter(null, increment.getTimeRange(), 1, + qualifiers); - ObTableQuery obTableQuery = buildObTableQuery(filter, rowKey, true, rowKey, true, false, increment.getTimeRange()); + ObTableQuery obTableQuery = buildObTableQuery(filter, rowKey, true, rowKey, true, false, increment.getTimeRange()); - ObTableQueryAndMutateRequest request = buildObTableQueryAndMutateRequest(obTableQuery, - batch, getTargetTableName(tableNameString, Bytes.toString(f), configuration)); - request.setReturningAffectedEntity(increment.isReturnResults()); - ObTableQueryAndMutateResult result = (ObTableQueryAndMutateResult) obTableClient - .execute(request); - if (!increment.isReturnResults()) { - return null; - } - ObTableQueryResult queryResult = result.getAffectedEntity(); - List keyValues = new ArrayList(); - for (List row : queryResult.getPropertiesRows()) { - byte[] k = (byte[]) row.get(0).getValue(); - byte[] q = (byte[]) row.get(1).getValue(); - long t = (Long) row.get(2).getValue(); - byte[] v = (byte[]) row.get(3).getValue(); - KeyValue kv = new KeyValue(k, f, q, t, v); - keyValues.add(kv); + ObTableQueryAndMutateRequest request = buildObTableQueryAndMutateRequest(obTableQuery, + batch, getTargetTableName(tableNameString, Bytes.toString(f), configuration), opType); + request.setReturningAffectedEntity(increment.isReturnResults()); + ObTableQueryAndMutateResult result = (ObTableQueryAndMutateResult) obTableClient + .execute(request); + if (!increment.isReturnResults()) { + return null; + } + ObTableQueryResult queryResult = result.getAffectedEntity(); + List keyValues = new ArrayList(); + for (List row : queryResult.getPropertiesRows()) { + byte[] k = (byte[]) row.get(0).getValue(); + byte[] q = (byte[]) row.get(1).getValue(); + long t = (Long) row.get(2).getValue(); + byte[] v = (byte[]) row.get(3).getValue(); + KeyValue kv = new KeyValue(k, f, q, t, v); + keyValues.add(kv); + } + return Result.create(keyValues); + } catch (Exception e) { + logger.error(LCD.convert("01-00007"), tableNameString, e); + throw new IOException("increment table " + tableNameString + " error.", e); + } } - return Result.create(keyValues); - } catch (Exception e) { - logger.error(LCD.convert("01-00007"), tableNameString, e); - throw new IOException("increment table " + tableNameString + " error.", e); - } + }); } /** @@ -1735,37 +1765,43 @@ public Result increment(Increment increment) throws IOException { @Override public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount) throws IOException { - try { - List qualifiers = new ArrayList(); - qualifiers.add(qualifier); - - ObTableBatchOperation batch = new ObTableBatchOperation(); - batch.addTableOperation(getInstance(INCREMENT, new Object[] { row, qualifier, - Long.MAX_VALUE }, V_COLUMNS, new Object[] { Bytes.toBytes(amount) })); - - ObHTableFilter filter = buildObHTableFilter(null, null, 1, qualifiers); - - ObTableQuery obTableQuery = buildObTableQuery(filter, row, true, row, true, false, - new TimeRange()); - ObTableQueryAndMutate queryAndMutate = new ObTableQueryAndMutate(); - queryAndMutate.setMutations(batch); - queryAndMutate.setTableQuery(obTableQuery); - - ObTableQueryAndMutateRequest request = buildObTableQueryAndMutateRequest(obTableQuery, - batch, getTargetTableName(tableNameString, Bytes.toString(family), configuration)); - request.setReturningAffectedEntity(true); - ObTableQueryAndMutateResult result = (ObTableQueryAndMutateResult) obTableClient - .execute(request); - ObTableQueryResult queryResult = result.getAffectedEntity(); - if (queryResult.getPropertiesRows().size() != 1) { - throw new IllegalStateException("the increment result size illegal " - + queryResult.getPropertiesRows().size()); + OHOperationType opType = OHOperationType.INCREMENT_COLUMN_VALUE; + return execute(new OperationExecuteCallback(opType, 1) { + @Override + Long execute() throws IOException { + try { + List qualifiers = new ArrayList(); + qualifiers.add(qualifier); + + ObTableBatchOperation batch = new ObTableBatchOperation(); + batch.addTableOperation(getInstance(INCREMENT, new Object[] { row, qualifier, + Long.MAX_VALUE }, V_COLUMNS, new Object[] { Bytes.toBytes(amount) })); + + ObHTableFilter filter = buildObHTableFilter(null, null, 1, qualifiers); + + ObTableQuery obTableQuery = buildObTableQuery(filter, row, true, row, true, false, + new TimeRange()); + ObTableQueryAndMutate queryAndMutate = new ObTableQueryAndMutate(); + queryAndMutate.setMutations(batch); + queryAndMutate.setTableQuery(obTableQuery); + + ObTableQueryAndMutateRequest request = buildObTableQueryAndMutateRequest(obTableQuery, + batch, getTargetTableName(tableNameString, Bytes.toString(family), configuration), opType); + request.setReturningAffectedEntity(true); + ObTableQueryAndMutateResult result = (ObTableQueryAndMutateResult) obTableClient + .execute(request); + ObTableQueryResult queryResult = result.getAffectedEntity(); + if (queryResult.getPropertiesRows().size() != 1) { + throw new IllegalStateException("the increment result size illegal " + + queryResult.getPropertiesRows().size()); + } + return Bytes.toLong((byte[]) queryResult.getPropertiesRows().get(0).get(3).getValue()); + } catch (Exception e) { + logger.error(LCD.convert("01-00007"), tableNameString, e); + throw new IOException("increment table " + tableNameString + " error.", e); + } } - return Bytes.toLong((byte[]) queryResult.getPropertiesRows().get(0).get(3).getValue()); - } catch (Exception e) { - logger.error(LCD.convert("01-00007"), tableNameString, e); - throw new IOException("increment table " + tableNameString + " error.", e); - } + }); } @Override @@ -2518,7 +2554,7 @@ private BatchOperation buildBatchOperation(String tableName, List return batch; } - private ObHbaseRequest buildHbaseRequest(List actions) + private ObHbaseRequest buildHbaseRequest(List actions, OHOperationType hbaseOpType) throws FeatureNotSupportedException, IllegalArgumentException, IOException { @@ -2574,6 +2610,7 @@ private ObHbaseRequest buildHbaseRequest(List actions) request.setTableName(tableNameString); request.setKeys(keys); request.setOpType(opType); + request.setHbaseOpType(hbaseOpType); request.setCfRows(cfRowsArray); request.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); return request; @@ -2614,18 +2651,21 @@ public static ObTableOperation buildObTableOperation(Cell kv, } private ObTableQueryRequest buildObTableQueryRequest(ObTableQuery obTableQuery, - String targetTableName) { + String targetTableName, + OHOperationType opType) { ObTableQueryRequest request = new ObTableQueryRequest(); request.setEntityType(ObTableEntityType.HKV); request.setTableQuery(obTableQuery); request.setTableName(targetTableName); request.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); request.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); + request.setHbaseOpType(opType); return request; } private ObTableQueryAsyncRequest buildObTableQueryAsyncRequest(ObTableQuery obTableQuery, - String targetTableName) { + String targetTableName, + OHOperationType opType) { ObTableQueryRequest request = new ObTableQueryRequest(); request.setEntityType(ObTableEntityType.HKV); request.setTableQuery(obTableQuery); @@ -2636,12 +2676,14 @@ private ObTableQueryAsyncRequest buildObTableQueryAsyncRequest(ObTableQuery obTa asyncRequest.setObTableQueryRequest(request); asyncRequest.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); asyncRequest.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); + asyncRequest.setHbaseOpType(opType); return asyncRequest; } private ObTableQueryAndMutateRequest buildObTableQueryAndMutateRequest(ObTableQuery obTableQuery, ObTableBatchOperation obTableBatchOperation, - String targetTableName) { + String targetTableName, + OHOperationType opType) { ObTableQueryAndMutate queryAndMutate = new ObTableQueryAndMutate(); queryAndMutate.setTableQuery(obTableQuery); queryAndMutate.setMutations(obTableBatchOperation); @@ -2652,6 +2694,7 @@ private ObTableQueryAndMutateRequest buildObTableQueryAndMutateRequest(ObTableQu request.setReturningAffectedEntity(true); request.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); request.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); + request.setHbaseOpType(opType); return request; } @@ -2829,14 +2872,8 @@ public boolean thenPut(Put put) throws IOException { checkCmpOp(); RowMutations rowMutations = new RowMutations(row); rowMutations.add(put); - try { - return checkAndMutation(row, family, qualifier, getCompareOp(cmpOp), value, - timeRange, rowMutations); - } catch (Exception e) { - logger.error(LCD.convert("01-00005"), rowMutations, tableNameString, e); - throw new IOException("checkAndMutate type table: " + tableNameString + " e.msg: " - + e.getMessage() + " error.", e); - } + return checkAndMutation(row, family, qualifier, getCompareOp(cmpOp), value, + timeRange, rowMutations, OHOperationType.CHECK_AND_PUT); } @Override @@ -2844,27 +2881,15 @@ public boolean thenDelete(Delete delete) throws IOException { checkCmpOp(); RowMutations rowMutations = new RowMutations(row); rowMutations.add(delete); - try { - return checkAndMutation(row, family, qualifier, getCompareOp(cmpOp), value, - timeRange, rowMutations); - } catch (Exception e) { - logger.error(LCD.convert("01-00005"), rowMutations, tableNameString, e); - throw new IOException("checkAndMutate type table: " + tableNameString + " e.msg: " - + e.getMessage() + " error.", e); - } + return checkAndMutation(row, family, qualifier, getCompareOp(cmpOp), value, + timeRange, rowMutations, OHOperationType.CHECK_AND_DELETE); } @Override public boolean thenMutate(RowMutations mutation) throws IOException { checkCmpOp(); - try { - return checkAndMutation(row, family, qualifier, getCompareOp(cmpOp), value, - timeRange, mutation); - } catch (Exception e) { - logger.error(LCD.convert("01-00005"), mutation, tableNameString, e); - throw new IOException("checkAndMutate type table: " + tableNameString + " e.msg: " - + e.getMessage() + " error.", e); - } + return checkAndMutation(row, family, qualifier, getCompareOp(cmpOp), value, + timeRange, mutation, OHOperationType.CHECK_AND_MUTATE); } private void checkCmpOp() { diff --git a/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java b/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java index b5f41c9c..4fe538ff 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java +++ b/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java @@ -17,9 +17,13 @@ package com.alipay.oceanbase.hbase.result; +import com.alipay.oceanbase.hbase.util.MetricsImporter; import com.alipay.oceanbase.hbase.util.OHBaseFuncUtils; +import com.alipay.oceanbase.hbase.util.OHMetrics; import com.alipay.oceanbase.hbase.util.TableHBaseLoggerFactory; +import com.alipay.oceanbase.rpc.location.model.partition.ObPair; import com.alipay.oceanbase.rpc.protocol.payload.impl.ObObj; +import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.query.AbstractQueryStreamResult; import com.alipay.oceanbase.rpc.stream.ObTableClientQueryAsyncStreamResult; import com.alipay.oceanbase.rpc.stream.ObTableClientQueryStreamResult; @@ -55,26 +59,32 @@ public class ClientStreamScanner extends AbstractClientScanner { private boolean isTableGroup = false; + private OHMetrics metrics; + public ClientStreamScanner(ObTableClientQueryStreamResult streamResult, String tableName, - Scan scan, boolean isTableGroup) { + Scan scan, boolean isTableGroup, OHMetrics metrics) { this.streamResult = streamResult; this.tableName = tableName; this.scan = scan; family = isTableGroup ? null : scan.getFamilyMap().entrySet().iterator().next().getKey(); this.isTableGroup = isTableGroup; + this.metrics = metrics; } public ClientStreamScanner(ObTableClientQueryAsyncStreamResult streamResult, String tableName, - Scan scan, boolean isTableGroup) { + Scan scan, boolean isTableGroup, OHMetrics metrics) { this.streamResult = streamResult; this.tableName = tableName; this.scan = scan; family = isTableGroup ? null : scan.getFamilyMap().entrySet().iterator().next().getKey(); this.isTableGroup = isTableGroup; + this.metrics = metrics; } @Override public Result next() throws IOException { + long startTimeMs = System.currentTimeMillis(); + MetricsImporter importer = metrics == null ? null : new MetricsImporter(); try { if (scan.getLimit() > 0 && lineCount++ >= scan.getLimit()) { close(); @@ -134,6 +144,13 @@ public Result next() throws IOException { logger.error(LCD.convert("01-00000"), streamResult.getTableName(), e); throw new IOException(String.format("get table %s stream next result error ", streamResult.getTableName()), e); + } finally { + if (metrics != null) { + long duration = System.currentTimeMillis() - startTimeMs; + importer.setDuration(duration); + importer.setSingleOpCount(1); + metrics.update(new ObPair(OHOperationType.SCAN, importer)); + } } } diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHBaseFuncUtils.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHBaseFuncUtils.java index 1a0ecd05..bda8e9a8 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHBaseFuncUtils.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHBaseFuncUtils.java @@ -19,6 +19,7 @@ import com.alipay.oceanbase.rpc.ObGlobal; import com.alipay.oceanbase.rpc.ObTableClient; +import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.client.Put; @@ -64,15 +65,18 @@ public static boolean isHBasePutPefSupport(ObTableClient tableClient) { } } - public static boolean isAllPut(List actions) { - boolean isAllPut = true; - for (Row action : actions) { - if (!(action instanceof Put)) { - isAllPut = false; - break; + public static boolean isAllPut(OHOperationType opType, List actions) { + if (opType.getValue() == OHOperationType.PUT.getValue() + || opType.getValue() == OHOperationType.PUT_LIST.getValue()) { + return true; + } else { + for (Row action : actions) { + if (!(action instanceof Put)) { + return false; + } } + return true; } - return isAllPut; } public static void sortHBaseResult(List cells) { diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java index 02fd8df7..af223705 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java @@ -23,10 +23,11 @@ public class OHMetrics { public OHMetrics(String metricsName) { this.metricsName = metricsName; this.registry = new MetricRegistry(); - trackers = new OHMetricsTracker[OHOperationType.values().length]; - for (int i = 0; i < trackers.length; ++i) { + trackers = new OHMetricsTracker[OHOperationType.values().length - 1]; + // OHOperationType(0) is INVALID, skip it + for (int i = 1; i < trackers.length; ++i) { OHOperationType opType = OHOperationType.valueOf(i); - trackers[i] = new OHMetricsTracker(this.registry, + trackers[i - 1] = new OHMetricsTracker(this.registry, metricsName, opType); } @@ -56,7 +57,7 @@ private void updateMetrics() { } private OHMetricsTracker getTracker(OHOperationType opType) { - return trackers[opType.getValue()]; + return trackers[opType.getValue() - 1]; } public String getMetricsName() { return this.metricsName; } From 7a21a281578bb6acfeaf38f342be60a85a87057e Mon Sep 17 00:00:00 2001 From: JackShi148 Date: Mon, 10 Nov 2025 20:48:03 +0800 Subject: [PATCH 03/15] fix metrics bug and add test interface --- .../com/alipay/oceanbase/hbase/OHTable.java | 11 +++++++- .../alipay/oceanbase/hbase/OHTableClient.java | 7 +++++ .../oceanbase/hbase/util/OHMetrics.java | 28 ++++++++++++++----- .../hbase/util/OHMetricsTracker.java | 4 +-- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index 480d9d50..788f76d2 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -64,6 +64,7 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.hbase.util.VersionInfo; +import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import java.io.ByteArrayOutputStream; @@ -1624,7 +1625,7 @@ Boolean execute() throws IOException { return result.getAffectedRows() > 0; } catch (Exception e) { logger.error(LCD.convert("01-00005"), rowMutations, tableNameString, e); - throw new IOException(opType.name() + " type table:" + tableNameString + " e.msg:" + throw new IOException(opType.toCamelCase() + " type table:" + tableNameString + " e.msg:" + e.getMessage() + " error.", e); } } @@ -1905,6 +1906,9 @@ public void close() throws IOException { if (cleanupPoolOnClose) { executePool.shutdown(); } + if (metrics != null) { + metrics.stop(); + } } @Override @@ -2898,4 +2902,9 @@ private void checkCmpOp() { + " ifNotExists/ifEquals/ifMatches before executing the request"); } } + + @VisibleForTesting + public OHMetrics getMetrics() { + return metrics; + } } \ No newline at end of file diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTableClient.java b/src/main/java/com/alipay/oceanbase/hbase/OHTableClient.java index 8a2c2f89..7a113b82 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTableClient.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTableClient.java @@ -18,6 +18,7 @@ package com.alipay.oceanbase.hbase; import com.alipay.oceanbase.hbase.core.Lifecycle; +import com.alipay.oceanbase.hbase.util.OHMetrics; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; import com.google.protobuf.Service; @@ -30,6 +31,7 @@ import org.apache.hadoop.hbase.filter.CompareFilter; import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel; import org.apache.hadoop.hbase.util.Pair; +import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.List; @@ -411,4 +413,9 @@ public Pair getStartEndKeys() throws IOException { checkStatus(); return this.ohTable.getStartEndKeys(); } + + @VisibleForTesting + public OHMetrics getMetrics() { + return ohTable.getMetrics(); + } } diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java index af223705..6cc41fa7 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java @@ -25,17 +25,17 @@ public OHMetrics(String metricsName) { this.registry = new MetricRegistry(); trackers = new OHMetricsTracker[OHOperationType.values().length - 1]; // OHOperationType(0) is INVALID, skip it - for (int i = 1; i < trackers.length; ++i) { + for (int i = 1; i <= trackers.length; ++i) { OHOperationType opType = OHOperationType.valueOf(i); trackers[i - 1] = new OHMetricsTracker(this.registry, - metricsName, - opType); + metricsName, + opType); } - this.reporter = JmxReporter.forRegistry(this.registry).build(); + this.reporter = JmxReporter.forRegistry(this.registry) + .inDomain("com.oceanbase.hbase.metrics") + .build(); this.reporter.start(); - } - public void start() { - scheduler.scheduleWithFixedDelay(this::updateMetrics, 10, 0, TimeUnit.SECONDS); + scheduler.scheduleWithFixedDelay(this::updateMetrics, 0, 10, TimeUnit.SECONDS); } // get the size of current queue,only update these metrics to corresponding trackers, ignore those concurrently added metrics private void updateMetrics() { @@ -71,4 +71,18 @@ public MetricsExporter acquireMetrics(OHOperationType opType) { OHMetricsTracker tracker = getTracker(opType); return tracker.acquireMetrics(); } + + public void stop() { + reporter.stop(); + try { + scheduler.shutdown(); + // wait at most 500 ms to close the scheduler + if (!scheduler.awaitTermination(500, TimeUnit.MILLISECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + logger.warn("scheduler await for terminate interrupted: {}.", e.getMessage()); + scheduler.shutdownNow(); + } + } } diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java index 73a0726c..1b250d80 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java @@ -43,8 +43,8 @@ public void update(MetricsImporter importer) { public MetricsExporter acquireMetrics() { long curTotalCount = this.latencyHistogram.getCount(); long curSingleOpCount = this.totalSingleOpCount.getCount(); - double averageSingleOpCount = ((double) curSingleOpCount) / ((double) curTotalCount); // the average number of single op per request - double failRate = this.failedOpCounter.getFiveMinuteRate(); // fail rate in 15 minutes + double averageSingleOpCount = curTotalCount == 0 ? 0 : ((double) curSingleOpCount) / ((double) curTotalCount); // the average number of single op per request + double failRate = this.failedOpCounter.getFiveMinuteRate(); // fail rate in 5 minutes return MetricsExporter.getInstanceOf(averageSingleOpCount, failRate, From 672530ebaf8fefd9fc622e24b83f539da8d4f4c8 Mon Sep 17 00:00:00 2001 From: JackShi148 Date: Thu, 13 Nov 2025 17:38:18 +0800 Subject: [PATCH 04/15] adapt batch put to origin hbase 2.x and add test case --- .../com/alipay/oceanbase/hbase/OHTable.java | 170 +-- .../oceanbase/hbase/util/MetricsExporter.java | 28 + .../hbase/util/OHMetricsTracker.java | 6 +- .../alipay/oceanbase/hbase/OHMetricsTest.java | 1185 +++++++++++++++++ 4 files changed, 1244 insertions(+), 145 deletions(-) create mode 100644 src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index 788f76d2..f8f29a4b 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -71,7 +71,6 @@ import java.io.IOException; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicLong; import static com.alipay.oceanbase.hbase.constants.OHConstants.*; import static com.alipay.oceanbase.hbase.util.Preconditions.checkArgument; @@ -154,36 +153,6 @@ public class OHTable implements Table { */ private boolean operationExecuteInPool = false; - /** - * the buffer of put request - */ - private final List writeBuffer = new CopyOnWriteArrayList<>(); - /** - * when the put request reach the write buffer size the do put will - * flush commits automatically - */ - private long writeBufferSize; - /** - * the do put check write buffer every putWriteBufferCheck puts - */ - private int putWriteBufferCheck; - - /** - * decide whether clear the buffer when meet exception.the default - * value is true. Be careful about the correctness when set it false - */ - private boolean clearBufferOnFail = true; - - /** - * whether flush the put automatically - */ - private boolean autoFlush = true; - - /** - * current buffer size - */ - private AtomicLong currentWriteBufferSize = new AtomicLong(0); - /** * the max size of put key value */ @@ -361,9 +330,6 @@ public OHTable(TableName tableName, Connection connection, HBASE_CLIENT_OPERATION_EXECUTE_IN_POOL, (this.operationTimeout != HConstants.DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT)); this.maxKeyValueSize = connectionConfig.getMaxKeyValueSize(); - this.putWriteBufferCheck = this.configuration.getInt(HBASE_HTABLE_PUT_WRITE_BUFFER_CHECK, - DEFAULT_HBASE_HTABLE_PUT_WRITE_BUFFER_CHECK); - this.writeBufferSize = connectionConfig.getWriteBufferSize(); this.tableName = tableName.getName(); int numRetries = connectionConfig.getNumRetries(); this.obTableClient = ObTableClientManager.getOrCreateObTableClient(setUserDefinedNamespace( @@ -412,9 +378,6 @@ public OHTable(Connection connection, ObTableBuilderBase builder, HBASE_CLIENT_OPERATION_EXECUTE_IN_POOL, (this.operationTimeout != HConstants.DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT)); this.maxKeyValueSize = connectionConfig.getMaxKeyValueSize(); - this.putWriteBufferCheck = this.configuration.getInt(HBASE_HTABLE_PUT_WRITE_BUFFER_CHECK, - DEFAULT_HBASE_HTABLE_PUT_WRITE_BUFFER_CHECK); - this.writeBufferSize = connectionConfig.getWriteBufferSize(); int numRetries = connectionConfig.getNumRetries(); this.obTableClient = ObTableClientManager.getOrCreateObTableClient(setUserDefinedNamespace( this.tableNameString, connectionConfig)); @@ -488,10 +451,6 @@ private void finishSetUp() { (this.operationTimeout != HConstants.DEFAULT_HBASE_CLIENT_OPERATION_TIMEOUT)); this.maxKeyValueSize = this.configuration.getInt(MAX_KEYVALUE_SIZE_KEY, MAX_KEYVALUE_SIZE_DEFAULT); - this.putWriteBufferCheck = this.configuration.getInt(HBASE_HTABLE_PUT_WRITE_BUFFER_CHECK, - DEFAULT_HBASE_HTABLE_PUT_WRITE_BUFFER_CHECK); - this.writeBufferSize = this.configuration.getLong(WRITE_BUFFER_SIZE_KEY, - WRITE_BUFFER_SIZE_DEFAULT); } public static OHConnectionConfiguration setUserDefinedNamespace(String tableNameString, @@ -552,11 +511,7 @@ private T execute(OperationExecuteCallback callback) throws IOException { } catch (Exception e) { // do not deal with any exception, just record importer.setIsFailedOp(true); // set as failed op - if (e instanceof IOException) { - throw (IOException) e; - } else { - throw new IOException("meet non-IOException in callback execute", e); - } + throw e; } finally { long duration = System.currentTimeMillis() - startTimeMs; importer.setDuration(duration); @@ -1396,29 +1351,14 @@ public Void execute() throws IOException { } private void doPut(List puts, OHOperationType opType) throws IOException { - int n = 0; for (Put put : puts) { validatePut(put); checkFamilyViolation(put.getFamilyCellMap().keySet(), true); - writeBuffer.add(put); - currentWriteBufferSize.addAndGet(put.heapSize()); - - // we need to periodically see if the writebuffer is full instead of waiting until the end of the List - n++; - if (n % putWriteBufferCheck == 0 && currentWriteBufferSize.get() > writeBufferSize) { - if (OHBaseFuncUtils.isHBasePutPefSupport(obTableClient)) { - flushCommitsV2(opType); - } else { - flushCommits(opType); - } - } } - if (autoFlush || currentWriteBufferSize.get() > writeBufferSize) { - if (OHBaseFuncUtils.isHBasePutPefSupport(obTableClient)) { - flushCommitsV2(opType); - } else { - flushCommits(opType); - } + if (OHBaseFuncUtils.isHBasePutPefSupport(obTableClient)) { + flushCommitsV2(puts, opType); + } else { + flushCommits(puts, opType); } } @@ -1811,92 +1751,34 @@ public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, lo return incrementColumnValue(row, family, qualifier, amount); } - public void flushCommits(OHOperationType opType) throws IOException { - + private void flushCommits(final List puts, OHOperationType opType) throws IOException { + if (puts.isEmpty()) { + return; + } try { - if (writeBuffer.isEmpty()) { - return; - } - Map exceptionRowMap = new LinkedHashMap(); - boolean[] resultSuccess = new boolean[writeBuffer.size()]; - try { - Object[] results = new Object[writeBuffer.size()]; - innerBatchImpl(writeBuffer, results, opType); - for (int i = 0; i != resultSuccess.length; ++i) { - if (results[i] instanceof ObTableException) { - resultSuccess[i] = false; - exceptionRowMap.put((ObTableException) results[i], writeBuffer.get(i)); - } else { - resultSuccess[i] = true; - } - } - } catch (Exception e) { - logger.error(LCD.convert("01-00008"), tableNameString, null, autoFlush, - writeBuffer.size(), e); - if (e instanceof IOException) { - throw (IOException) e; - } - } finally { - // mutate list so that it is empty for complete success, or contains - // only failed records results are returned in the same order as the - // requests in list walk the list backwards, so we can remove from list - // without impacting the indexes of earlier members - for (int i = resultSuccess.length - 1; i >= 0; i--) { - if (resultSuccess[i]) { - // successful Puts are removed from the list here. - writeBuffer.remove(i); - } - } - if (!exceptionRowMap.isEmpty()) { - for (Map.Entry entry : exceptionRowMap.entrySet()) { - logger.error(LCD.convert("01-00008"), entry.getValue(), tableNameString, - autoFlush, writeBuffer.size(), entry.getKey()); - } - } - } - } finally { - if (clearBufferOnFail) { - writeBuffer.clear(); - currentWriteBufferSize.set(0); - } else { - // the write buffer was adjusted by processBatchOfPuts - currentWriteBufferSize.set(0); - for (Put aPut : writeBuffer) { - currentWriteBufferSize.addAndGet(aPut.heapSize()); - } + Object[] results = new Object[puts.size()]; + innerBatchImpl(puts, results, opType); + } catch (Exception e) { + logger.error(LCD.convert("01-00008"), tableNameString, null, puts.size(), e); + if (e instanceof IOException) { + throw (IOException) e; } } } - public void flushCommitsV2(OHOperationType opType) throws IOException { + private void flushCommitsV2(final List puts, OHOperationType opType) throws IOException { + if (puts.isEmpty()) { + return; + } try { - if (writeBuffer.isEmpty()) { - return; - } - try { - ObHbaseRequest request = buildHbaseRequest(writeBuffer, opType); - try { - ObHbaseResult result = (ObHbaseResult) obTableClient.execute(request); - } catch (Exception e) { - throw new IOException(tableNameString + " table occurred unexpected error.", e); - } - } catch (Exception e) { - logger.error(LCD.convert("01-00008"), tableNameString, null, autoFlush, - writeBuffer.size(), e); - if (e instanceof IOException) { - throw (IOException) e; - } - } - } finally { - if (clearBufferOnFail) { - writeBuffer.clear(); - currentWriteBufferSize.set(0); + ObHbaseRequest request = buildHbaseRequest(puts, opType); + ObHbaseResult result = (ObHbaseResult) obTableClient.execute(request); + } catch (Exception e) { + logger.error(LCD.convert("01-00008"), tableNameString, null, puts.size(), e); + if (e instanceof IOException) { + throw (IOException) e; } else { - // the write buffer was adjusted by processBatchOfPuts - currentWriteBufferSize.set(0); - for (Put aPut : writeBuffer) { - currentWriteBufferSize.addAndGet(aPut.heapSize()); - } + throw new IOException(tableNameString + " table occurred unexpected error.", e); } } } diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java index fcd0c95a..bfefb240 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java @@ -5,12 +5,16 @@ public class MetricsExporter { private double failRate; + private long failCount; + private long totalRuntime; private double averageSingleOpCount; private Timer latencyHistogram; // for p99 private Snapshot latencySnapshot; public MetricsExporter() { this.failRate = 0; + this.failCount = 0L; + this.totalRuntime = 0L; this.averageSingleOpCount = 0; this.latencyHistogram = null; this.latencySnapshot = null; @@ -20,6 +24,14 @@ public void setFailRate(double failRate) { this.failRate = failRate; } + public void setFailCount(long failCount) { + this.failCount = failCount; + } + + public void setTotalRuntime(long totalRuntime) { + this.totalRuntime = totalRuntime; + } + public void setAverageSingleOpCount(double averageSingleOpCount) { this.averageSingleOpCount = averageSingleOpCount; } @@ -32,6 +44,10 @@ public void setLatencySnapshot(Snapshot latencySnapshot) { this.latencySnapshot = latencySnapshot; } + public long getCount() { + return latencyHistogram.getCount(); + } + public double getAverageOps() { return latencyHistogram.getMeanRate(); } @@ -52,6 +68,14 @@ public double getFailRate() { return failRate; } + public long getFailCount() { + return failCount; + } + + public long getTotalRuntime() { + return totalRuntime; + } + public double getAverageSingleOpCount() { return averageSingleOpCount; } @@ -94,10 +118,14 @@ public double get999thPercentile() { public static MetricsExporter getInstanceOf(double averageSingleOpCount, double failRate, + long failCount, + long totalRuntime, Timer latencyHistogram) { MetricsExporter exporter = new MetricsExporter(); exporter.setAverageSingleOpCount(averageSingleOpCount); exporter.setFailRate(failRate); + exporter.setFailCount(failCount); + exporter.setTotalRuntime(totalRuntime); exporter.setLatencyHistogram(latencyHistogram); exporter.setLatencySnapshot(latencyHistogram.getSnapshot()); return exporter; diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java index 1b250d80..e152e29d 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java @@ -44,10 +44,14 @@ public MetricsExporter acquireMetrics() { long curTotalCount = this.latencyHistogram.getCount(); long curSingleOpCount = this.totalSingleOpCount.getCount(); double averageSingleOpCount = curTotalCount == 0 ? 0 : ((double) curSingleOpCount) / ((double) curTotalCount); // the average number of single op per request - double failRate = this.failedOpCounter.getFiveMinuteRate(); // fail rate in 5 minutes + long failCount = this.failedOpCounter.getCount(); + long runtime = this.totalRuntime.getCount(); + double failRate = this.failedOpCounter.getMeanRate(); // fail rate return MetricsExporter.getInstanceOf(averageSingleOpCount, failRate, + failCount, + runtime, latencyHistogram); } } diff --git a/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java b/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java new file mode 100644 index 00000000..17e77f91 --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java @@ -0,0 +1,1185 @@ +package com.alipay.oceanbase.hbase; + +import com.alipay.oceanbase.hbase.util.MetricsExporter; +import com.alipay.oceanbase.hbase.util.OHMetrics; +import com.alipay.oceanbase.hbase.util.ObHTableTestUtil; +import com.alipay.oceanbase.rpc.exception.ObTableUnexpectedException; +import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.*; +import org.apache.hadoop.hbase.client.coprocessor.Batch; +import org.apache.hadoop.hbase.filter.CompareFilter; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.*; + +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import static org.apache.hadoop.hbase.client.MetricsConnection.CLIENT_SIDE_METRICS_ENABLED_KEY; + +public class OHMetricsTest { + protected static Connection connection; + protected static Table hTable; + + /* + CREATE TABLEGROUP test_multi_cf SHARDING = 'ADAPTIVE'; + CREATE TABLE `test_multi_cf$family_with_group1` ( + `K` varbinary(1024) NOT NULL, + `Q` varbinary(256) NOT NULL, + `T` bigint(20) NOT NULL, + `V` varbinary(1024) DEFAULT NULL, + PRIMARY KEY (`K`, `Q`, `T`) + ) TABLEGROUP = test_multi_cf PARTITION BY KEY(`K`) PARTITIONS 3; + + CREATE TABLE `test_multi_cf$family_with_group2` ( + `K` varbinary(1024) NOT NULL, + `Q` varbinary(256) NOT NULL, + `T` bigint(20) NOT NULL, + `V` varbinary(1024) DEFAULT NULL, + PRIMARY KEY (`K`, `Q`, `T`) + ) TABLEGROUP = test_multi_cf PARTITION BY KEY(`K`) PARTITIONS 3; + + CREATE TABLE `test_multi_cf$family_with_group3` ( + `K` varbinary(1024) NOT NULL, + `Q` varbinary(256) NOT NULL, + `T` bigint(20) NOT NULL, + `V` varbinary(1024) DEFAULT NULL, + PRIMARY KEY (`K`, `Q`, `T`) + ) TABLEGROUP = test_multi_cf PARTITION BY KEY(`K`) PARTITIONS 3; + * */ + @BeforeClass + public static void setUp() throws Exception { + Configuration conf = ObHTableTestUtil.newConfiguration(); + conf.set(CLIENT_SIDE_METRICS_ENABLED_KEY, "true"); + connection = ConnectionFactory.createConnection(conf); + hTable = connection.getTable(TableName.valueOf("test_multi_cf")); + } + + @After + public void cleanUp() throws Exception { + java.sql.Connection sqlConn = ObHTableTestUtil.getConnection(); + Statement s = sqlConn.createStatement(); + s.execute("truncate table test_multi_cf$family_with_group1"); + s.execute("truncate table test_multi_cf$family_with_group2"); + s.execute("truncate table test_multi_cf$family_with_group3"); + s.close(); + sqlConn.close(); + } + + @AfterClass + public static void finish() throws Exception { + if (hTable != null) { + hTable.close(); + } + if (connection != null) { + connection.close(); + } + } + + @Test + public void testPutOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + String family2 = "family_with_group2"; + String family3 = "family_with_group3"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.PUT); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + put.addColumn(Bytes.toBytes(family2), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + put.addColumn(Bytes.toBytes(family3), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.PUT); + System.out.println("PUT - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("PUT - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("PUT - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("PUT - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("PUT - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("PUT - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("PUT - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + put = new Put(Bytes.toBytes(row1)); + hTable.put(put); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("No columns")); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.PUT); + System.out.println("PUT - FailCount: " + newExporter.getFailCount()); + Assert.assertEquals(1L, newExporter.getFailCount()); + } + + @Test + public void testGetOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.GET); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + + Get get = new Get(Bytes.toBytes(row1)); + hTable.get(get); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.GET); + System.out.println("GET - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("GET - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("GET - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("GET - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("GET - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("GET - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("GET - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.get(get); + } catch (Exception e) { + System.out.println("GET error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.GET); + System.out.println("GET - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testGetListOperationsMetrics() throws Exception { + String row1 = "row1"; + String row2 = "row2"; + String row3 = "row3"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.GET_LIST); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put1 = new Put(Bytes.toBytes(row1)); + put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + Put put2 = new Put(Bytes.toBytes(row2)); + put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val2")); + Put put3 = new Put(Bytes.toBytes(row3)); + put3.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val3")); + hTable.put(put1); + hTable.put(put2); + hTable.put(put3); + + List gets = Arrays.asList( + new Get(Bytes.toBytes(row1)), + new Get(Bytes.toBytes(row2)), + new Get(Bytes.toBytes(row3)) + ); + hTable.get(gets); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.GET_LIST); + System.out.println("GET_LIST - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("GET_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); + System.out.println("GET_LIST - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("GET_LIST - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("GET_LIST - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("GET_LIST - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("GET_LIST - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.get(gets); + } catch (Exception e) { + System.out.println("GET_LIST error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.GET_LIST); + System.out.println("GET_LIST - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testPutListOperationsMetrics() throws Exception { + String row1 = "row1"; + String row2 = "row2"; + String row3 = "row3"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + List puts = new ArrayList<>(); + Put put1 = new Put(Bytes.toBytes(row1)); + put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + puts.add(put1); + Put put2 = new Put(Bytes.toBytes(row2)); + put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val2")); + puts.add(put2); + Put put3 = new Put(Bytes.toBytes(row3)); + put3.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val3")); + puts.add(put3); + + hTable.put(puts); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); + System.out.println("PUT_LIST - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("PUT_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); + System.out.println("PUT_LIST - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("PUT_LIST - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("PUT_LIST - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("PUT_LIST - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("PUT_LIST - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + put1 = new Put(Bytes.toBytes(row1)); + put2 = new Put(Bytes.toBytes(row2)); + puts.clear(); + puts.add(put1); + puts.add(put2); + hTable.put(puts); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("No columns")); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); + System.out.println("PUT_LIST - FailCount: " + newExporter.getFailCount()); + Assert.assertEquals(1L, newExporter.getFailCount()); + } + + @Test + public void testDeleteOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.DELETE); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + + Delete delete = new Delete(Bytes.toBytes(row1)); + delete.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1")); + hTable.delete(delete); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.DELETE); + System.out.println("DELETE - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("DELETE - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("DELETE - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("DELETE - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("DELETE - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("DELETE - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("DELETE - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + delete = new Delete((byte[]) null); + hTable.delete(delete); + } catch (Exception e) { + System.out.println("DELETE error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.DELETE); + System.out.println("DELETE - FailCount: " + newExporter.getFailCount()); + Assert.assertEquals(1L, newExporter.getFailCount()); + } + + @Test + public void testDeleteListOperationsMetrics() throws Exception { + String row1 = "row1"; + String row2 = "row2"; + String row3 = "row3"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.DELETE_LIST); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put1 = new Put(Bytes.toBytes(row1)); + put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + Put put2 = new Put(Bytes.toBytes(row2)); + put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val2")); + Put put3 = new Put(Bytes.toBytes(row3)); + put3.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val3")); + hTable.put(put1); + hTable.put(put2); + hTable.put(put3); + + List deletes = new ArrayList<>(); + Delete delete1 = new Delete(Bytes.toBytes(row1)); + delete1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1")); + deletes.add(delete1); + Delete delete2 = new Delete(Bytes.toBytes(row2)); + delete2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1")); + deletes.add(delete2); + Delete delete3 = new Delete(Bytes.toBytes(row3)); + delete3.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1")); + deletes.add(delete3); + + hTable.delete(deletes); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.DELETE_LIST); + System.out.println("DELETE_LIST - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("DELETE_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); + System.out.println("DELETE_LIST - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("DELETE_LIST - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("DELETE_LIST - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("DELETE_LIST - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("DELETE_LIST - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.delete(deletes); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("DELETE_LIST error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.DELETE_LIST); + System.out.println("DELETE_LIST - FailCount: " + newExporter.getFailCount()); + Assert.assertEquals(1L, newExporter.getFailCount()); + } + + @Test + public void testExistsOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.EXISTS); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + + Get get = new Get(Bytes.toBytes(row1)); + boolean exists = hTable.exists(get); + Assert.assertTrue(exists); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.EXISTS); + System.out.println("EXISTS - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("EXISTS - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("EXISTS - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("EXISTS - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("EXISTS - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("EXISTS - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("EXISTS - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + get = new Get(Bytes.toBytes(row1)); + notExistTable.exists(get); + } catch (Exception e) { + System.out.println("EXISTS error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.EXISTS); + System.out.println("EXISTS - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testExistsListOperationsMetrics() throws Exception { + String row1 = "row1"; + String row2 = "row2"; + String row3 = "row3"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.EXISTS_LIST); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put1 = new Put(Bytes.toBytes(row1)); + put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + Put put2 = new Put(Bytes.toBytes(row2)); + put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val2")); + hTable.put(put1); + hTable.put(put2); + + List gets = Arrays.asList( + new Get(Bytes.toBytes(row1)), + new Get(Bytes.toBytes(row2)), + new Get(Bytes.toBytes(row3)) + ); + boolean[] exists = hTable.exists(gets); + Assert.assertTrue(exists[0]); + Assert.assertTrue(exists[1]); + Assert.assertFalse(exists[2]); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.EXISTS_LIST); + System.out.println("EXISTS_LIST - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("EXISTS_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); + System.out.println("EXISTS_LIST - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("EXISTS_LIST - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("EXISTS_LIST - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("EXISTS_LIST - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("EXISTS_LIST - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.exists(gets); + } catch (Exception e) { + System.out.println("EXISTS_LIST error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.EXISTS_LIST); + System.out.println("EXISTS_LIST - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testScanOperationsMetrics() throws Exception { + String row1 = "row1"; + String row2 = "row2"; + String row3 = "row3"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.SCAN); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put1 = new Put(Bytes.toBytes(row1)); + put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + Put put2 = new Put(Bytes.toBytes(row2)); + put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val2")); + Put put3 = new Put(Bytes.toBytes(row3)); + put3.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val3")); + hTable.put(put1); + hTable.put(put2); + hTable.put(put3); + + Scan scan = new Scan(); + scan.addFamily(Bytes.toBytes(family1)); + ResultScanner scanner = hTable.getScanner(scan); + int count = 0; + for (Result result : scanner) { + count += result.rawCells().length; + } + scanner.close(); + Assert.assertEquals(3, count); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.SCAN); + System.out.println("SCAN - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("SCAN - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("SCAN - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("SCAN - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("SCAN - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("SCAN - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("SCAN - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.getScanner(scan); + } catch (Exception e) { + System.out.println("SCAN error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.SCAN); + System.out.println("SCAN - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testBatchOperationsMetrics() throws Exception { + String row1 = "row1"; + String row2 = "row2"; + String row3 = "row3"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.BATCH); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + List actions = new ArrayList<>(); + Put put1 = new Put(Bytes.toBytes(row1)); + put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + actions.add(put1); + Put put2 = new Put(Bytes.toBytes(row2)); + put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val2")); + actions.add(put2); + Get get1 = new Get(Bytes.toBytes(row3)); + actions.add(get1); + + Object[] results = new Object[actions.size()]; + hTable.batch(actions, results); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.BATCH); + System.out.println("BATCH - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("BATCH - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); + System.out.println("BATCH - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("BATCH - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("BATCH - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("BATCH - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("BATCH - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + results = new Object[actions.size()]; + notExistTable.batch(actions, results); + } catch (Exception e) { + System.out.println("BATCH error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.BATCH); + System.out.println("BATCH - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testBatchCallbackOperationsMetrics() throws Exception { + String row1 = "row1"; + String row2 = "row2"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.BATCH_CALLBACK); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + List actions = new ArrayList<>(); + Put put1 = new Put(Bytes.toBytes(row1)); + put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + actions.add(put1); + Put put2 = new Put(Bytes.toBytes(row2)); + put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val2")); + actions.add(put2); + + Object[] results = new Object[actions.size()]; + hTable.batchCallback(actions, results, new Batch.Callback() { + @Override + public void update(byte[] region, byte[] row, Result result) { + // Do nothing + } + }); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.BATCH_CALLBACK); + System.out.println("BATCH_CALLBACK - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("BATCH_CALLBACK - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(2.0, newExporter.getAverageSingleOpCount(), 0.1); + System.out.println("BATCH_CALLBACK - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("BATCH_CALLBACK - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("BATCH_CALLBACK - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("BATCH_CALLBACK - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("BATCH_CALLBACK - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + results = new Object[actions.size()]; + notExistTable.batchCallback(actions, results, new Batch.Callback() { + @Override + public void update(byte[] region, byte[] row, Result result) { + // Do nothing + } + }); + } catch (Exception e) { + System.out.println("BATCH_CALLBACK error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.BATCH_CALLBACK); + System.out.println("BATCH_CALLBACK - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testCheckAndPutOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_PUT); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put1 = new Put(Bytes.toBytes(row1)); + put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put1); + + Put put2 = new Put(Bytes.toBytes(row1)); + put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q2"), Bytes.toBytes("val2")); + boolean result = hTable.checkAndPut(Bytes.toBytes(row1), Bytes.toBytes(family1), + Bytes.toBytes("q1"), Bytes.toBytes("val1"), put2); + Assert.assertTrue(result); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_PUT); + System.out.println("CHECK_AND_PUT - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("CHECK_AND_PUT - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("CHECK_AND_PUT - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("CHECK_AND_PUT - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("CHECK_AND_PUT - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("CHECK_AND_PUT - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("CHECK_AND_PUT - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + Put put3 = new Put(Bytes.toBytes(row1)); + put3.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q3"), Bytes.toBytes("val3")); + notExistTable.checkAndPut(Bytes.toBytes(row1), Bytes.toBytes(family1), + Bytes.toBytes("q2"), Bytes.toBytes("val2"), put3); + } catch (Exception e) { + System.out.println("CHECK_AND_PUT error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_PUT); + System.out.println("CHECK_AND_PUT - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testCheckAndDeleteOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_DELETE); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + + Delete delete = new Delete(Bytes.toBytes(row1)); + delete.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q2")); + boolean result = hTable.checkAndDelete(Bytes.toBytes(row1), Bytes.toBytes(family1), + Bytes.toBytes("q1"), Bytes.toBytes("val1"), delete); + Assert.assertTrue(result); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_DELETE); + System.out.println("CHECK_AND_DELETE - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("CHECK_AND_DELETE - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("CHECK_AND_DELETE - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("CHECK_AND_DELETE - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("CHECK_AND_DELETE - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("CHECK_AND_DELETE - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("CHECK_AND_DELETE - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.checkAndDelete(Bytes.toBytes(row1), Bytes.toBytes(family1), + Bytes.toBytes("q1"), Bytes.toBytes("val1"), delete); + } catch (Exception e) { + System.out.println("CHECK_AND_DELETE error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_DELETE); + System.out.println("CHECK_AND_DELETE - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testCheckAndMutateOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_MUTATE); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + // Prepare data first + Put put1 = new Put(Bytes.toBytes(row1)); + put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put1); + + Put put2 = new Put(Bytes.toBytes(row1)); + put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q2"), Bytes.toBytes("val2")); + Delete delete = new Delete(Bytes.toBytes(row1)); + delete.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q3")); + RowMutations rowMutations = new RowMutations(Bytes.toBytes(row1)); + rowMutations.add(put2); + rowMutations.add(delete); + + boolean result = hTable.checkAndMutate(Bytes.toBytes(row1), Bytes.toBytes(family1), + Bytes.toBytes("q1"), CompareFilter.CompareOp.EQUAL, Bytes.toBytes("val1"), rowMutations); + Assert.assertTrue(result); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_MUTATE); + System.out.println("CHECK_AND_MUTATE - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("CHECK_AND_MUTATE - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(2.0, newExporter.getAverageSingleOpCount(), 0.1); + System.out.println("CHECK_AND_MUTATE - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("CHECK_AND_MUTATE - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("CHECK_AND_MUTATE - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("CHECK_AND_MUTATE - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("CHECK_AND_MUTATE - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.checkAndMutate(Bytes.toBytes(row1), Bytes.toBytes(family1), + Bytes.toBytes("q1"), CompareFilter.CompareOp.EQUAL, Bytes.toBytes("val1"), rowMutations); + } catch (Exception e) { + System.out.println("CHECK_AND_MUTATE error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_MUTATE); + System.out.println("CHECK_AND_MUTATE - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testAppendOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.APPEND); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + Append append = new Append(Bytes.toBytes(row1)); + append.add(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.append(append); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.APPEND); + System.out.println("APPEND - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("APPEND - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("APPEND - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("APPEND - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("APPEND - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("APPEND - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("APPEND - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.append(append); + } catch (Exception e) { + System.out.println("APPEND error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.APPEND); + System.out.println("APPEND - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testIncrementOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.INCREMENT); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + Increment increment = new Increment(Bytes.toBytes(row1)); + increment.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), 10L); + hTable.increment(increment); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.INCREMENT); + System.out.println("APPEND - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("APPEND - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("APPEND - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("APPEND - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("APPEND - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("APPEND - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("APPEND - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.increment(increment); + } catch (Exception e) { + System.out.println("INCREMENT error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.INCREMENT); + System.out.println("INCREMENT - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testIncrementColumnValueOperationsMetrics() throws Exception { + String row1 = "row1"; + String family1 = "family_with_group1"; + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.INCREMENT_COLUMN_VALUE); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); + Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); + Assert.assertEquals(0, oldExporter.getTotalRuntime()); + + long res = hTable.incrementColumnValue(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q1"), 10L); + Assert.assertEquals(10L, res); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.INCREMENT_COLUMN_VALUE); + System.out.println("INCREMENT_COLUMN_VALUE - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("INCREMENT_COLUMN_VALUE - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); + System.out.println("INCREMENT_COLUMN_VALUE - AverageLatency: " + newExporter.getAverageLatency()); + Assert.assertTrue(newExporter.getAverageLatency() > 0); + System.out.println("INCREMENT_COLUMN_VALUE - MaxLatency: " + newExporter.getMaxLatency()); + Assert.assertTrue(newExporter.getMaxLatency() > 0); + System.out.println("INCREMENT_COLUMN_VALUE - MinLatency: " + newExporter.getMinLatency()); + Assert.assertTrue(newExporter.getMinLatency() > 0); + System.out.println("INCREMENT_COLUMN_VALUE - 99thPercentile: " + newExporter.get99thPercentile()); + Assert.assertTrue(newExporter.get99thPercentile() > 0); + System.out.println("INCREMENT_COLUMN_VALUE - TotalRuntime: " + newExporter.getTotalRuntime()); + Assert.assertTrue(newExporter.getTotalRuntime() > 0); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.incrementColumnValue(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q1"), 10L); + } catch (Exception e) { + System.out.println("INCREMENT_COLUMN_VALUE error: " + e.getMessage()); + } + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.INCREMENT_COLUMN_VALUE); + System.out.println("INCREMENT_COLUMN_VALUE - FailCount: " + newExporter.getFailCount()); + Assert.assertTrue(newExporter.getFailCount() >= 0); + } + + @Test + public void testEmptyBatchMetrics() throws Exception { + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + // test put list + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + List puts = new ArrayList<>(); + hTable.put(puts); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); + System.out.println("PUT_LIST - AverageOps: " + newExporter.getAverageOps()); + // average ops will larger than 0 even if the list is empty + // because the metrics of ops is recorded for put method + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("PUT_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(0.0, newExporter.getAverageSingleOpCount(), 0.001); + + // test batch + oldExporter = metrics.acquireMetrics(OHOperationType.BATCH); + Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); + Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); + List actions = new ArrayList<>(); + Object[] results = new Object[actions.size()]; + hTable.batch(actions, results); + Thread.sleep(11000); // sleep over 10 second + newExporter = metrics.acquireMetrics(OHOperationType.BATCH); + System.out.println("BATCH - AverageOps: " + newExporter.getAverageOps()); + Assert.assertTrue(newExporter.getAverageOps() > 0); + System.out.println("BATCH - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); + Assert.assertEquals(0.0, newExporter.getAverageSingleOpCount(), 0.001); + } + + @Test + public void testConcurrentOperationMetrics() throws Exception { + byte[] family1 = Bytes.toBytes("family_with_group1"); + OHMetrics metrics = ((OHTable) hTable).getMetrics(); + if (metrics == null) { + throw new ObTableUnexpectedException("unexpected null metrics"); + } + int threadCount = 10; + int operationsPerThread = 100; + CountDownLatch latch = new CountDownLatch(threadCount); + + for (int i = 0; i < threadCount; i++) { + int threadId = i; + new Thread(() -> { + try { + for (int j = 0; j < operationsPerThread; j++) { + Put put = new Put(Bytes.toBytes("row_" + threadId + "_" + j)); + put.addColumn(family1, Bytes.toBytes("q_" + threadId + "_" + j), Bytes.toBytes("val1")); + hTable.put(put); + } + } catch (Exception e) { + // handle + } finally { + latch.countDown(); + } + }).start(); + } + + latch.await(); + Scan scan = new Scan(); + scan.readVersions(10); + scan.setBatch(10); + scan.addFamily(family1); + ResultScanner scanner = hTable.getScanner(scan); + int cellCount = 0; + for (Result res : scanner) { + cellCount += res.rawCells().length; + } + Assert.assertEquals(threadCount * operationsPerThread, cellCount); + Thread.sleep(11000); + + MetricsExporter exporter = metrics.acquireMetrics(OHOperationType.PUT); + long expectedCount = threadCount * operationsPerThread; + System.out.println("exporter count: " + exporter.getCount()); + System.out.println("expected count: " + expectedCount); + Assert.assertTrue(exporter.getCount() == expectedCount); + } +} From 9a42a5002d8b353d70ab7bbaed994f0deb4e8ae4 Mon Sep 17 00:00:00 2001 From: JackShi148 Date: Fri, 14 Nov 2025 15:58:13 +0800 Subject: [PATCH 05/15] add new jmx test and metrics exporter test --- .../oceanbase/hbase/util/MetricsExporter.java | 81 ++- .../oceanbase/hbase/JMXMetricsTest.java | 311 +++++++++ .../alipay/oceanbase/hbase/OHMetricsTest.java | 602 +++++------------- .../hbase/util/JMXMetricsTestHelper.java | 233 +++++++ 4 files changed, 757 insertions(+), 470 deletions(-) create mode 100644 src/test/java/com/alipay/oceanbase/hbase/JMXMetricsTest.java create mode 100644 src/test/java/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java index bfefb240..c32b15d2 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java @@ -4,20 +4,47 @@ import com.codahale.metrics.Timer; public class MetricsExporter { - private double failRate; + private final long totalOpCount; + private final double averageOps; + private final double oneMinuteAverageOps; + private final double fiveMinuteAverageOps; + private final double fifteenMinuteAverageOps; + private final double averageLatency; // ms + private final long maxLatency; // ms + private final long minLatency; // ms + private final double medianLatency; // ms + private final double P75thPercentile; // ms + private final double P95thPercentile; // ms + private final double P98thPercentile; // ms + private final double P99thPercentile; // ms + private final double P999thPercentile; // ms private long failCount; - private long totalRuntime; + private long totalRuntime; // ms + private double failRate; private double averageSingleOpCount; - private Timer latencyHistogram; // for p99 - private Snapshot latencySnapshot; - public MetricsExporter() { + public MetricsExporter(Timer latencyHistogram) { + this.totalOpCount = latencyHistogram.getCount(); + this.averageOps = latencyHistogram.getMeanRate(); + this.oneMinuteAverageOps = latencyHistogram.getOneMinuteRate(); + this.fiveMinuteAverageOps = latencyHistogram.getFiveMinuteRate(); + this.fifteenMinuteAverageOps = latencyHistogram.getFifteenMinuteRate(); + Snapshot snapshot = latencyHistogram.getSnapshot(); + // Time unit of duration stored in Timer is nanosecond, convert it to millisecond + double nanosecondsToMilliseconds = 1_000_000.0; + this.averageLatency = snapshot.getMean() / nanosecondsToMilliseconds; + this.maxLatency = (long) (snapshot.getMax() / nanosecondsToMilliseconds); + this.minLatency = (long) (snapshot.getMin() / nanosecondsToMilliseconds); + this.medianLatency = snapshot.getMedian() / nanosecondsToMilliseconds; + this.P75thPercentile = snapshot.get75thPercentile() / nanosecondsToMilliseconds; + this.P95thPercentile = snapshot.get95thPercentile() / nanosecondsToMilliseconds; + this.P98thPercentile = snapshot.get98thPercentile() / nanosecondsToMilliseconds; + this.P99thPercentile = snapshot.get99thPercentile() / nanosecondsToMilliseconds; + this.P999thPercentile = snapshot.get999thPercentile() / nanosecondsToMilliseconds; this.failRate = 0; this.failCount = 0L; this.totalRuntime = 0L; this.averageSingleOpCount = 0; - this.latencyHistogram = null; - this.latencySnapshot = null; } public void setFailRate(double failRate) { @@ -36,32 +63,24 @@ public void setAverageSingleOpCount(double averageSingleOpCount) { this.averageSingleOpCount = averageSingleOpCount; } - public void setLatencyHistogram(Timer latencyHistogram) { - this.latencyHistogram = latencyHistogram; - } - - public void setLatencySnapshot(Snapshot latencySnapshot) { - this.latencySnapshot = latencySnapshot; - } - public long getCount() { - return latencyHistogram.getCount(); + return totalOpCount; } public double getAverageOps() { - return latencyHistogram.getMeanRate(); + return averageOps; } public double getOneMinuteAverageOps() { - return latencyHistogram.getOneMinuteRate(); + return oneMinuteAverageOps; } public double getFiveMinuteAverageOps() { - return latencyHistogram.getFiveMinuteRate(); + return fiveMinuteAverageOps; } public double getFifteenMinuteAverageOps() { - return latencyHistogram.getFifteenMinuteRate(); + return fifteenMinuteAverageOps; } public double getFailRate() { @@ -81,39 +100,39 @@ public double getAverageSingleOpCount() { } public double getAverageLatency() { - return latencySnapshot.getMean(); + return averageLatency; } public long getMaxLatency() { - return latencySnapshot.getMax(); + return maxLatency; } public long getMinLatency() { - return latencySnapshot.getMin(); + return minLatency; } public double getMedian() { - return latencySnapshot.getMedian(); + return medianLatency; } public double get75thPercentile() { - return latencySnapshot.get75thPercentile(); + return P75thPercentile; } public double get95thPercentile() { - return latencySnapshot.get95thPercentile(); + return P95thPercentile; } public double get98thPercentile() { - return latencySnapshot.get98thPercentile(); + return P98thPercentile; } public double get99thPercentile() { - return latencySnapshot.get99thPercentile(); + return P99thPercentile; } public double get999thPercentile() { - return latencySnapshot.get999thPercentile(); + return P999thPercentile; } public static MetricsExporter getInstanceOf(double averageSingleOpCount, @@ -121,13 +140,11 @@ public static MetricsExporter getInstanceOf(double averageSingleOpCount, long failCount, long totalRuntime, Timer latencyHistogram) { - MetricsExporter exporter = new MetricsExporter(); + MetricsExporter exporter = new MetricsExporter(latencyHistogram); exporter.setAverageSingleOpCount(averageSingleOpCount); exporter.setFailRate(failRate); exporter.setFailCount(failCount); exporter.setTotalRuntime(totalRuntime); - exporter.setLatencyHistogram(latencyHistogram); - exporter.setLatencySnapshot(latencyHistogram.getSnapshot()); return exporter; } } diff --git a/src/test/java/com/alipay/oceanbase/hbase/JMXMetricsTest.java b/src/test/java/com/alipay/oceanbase/hbase/JMXMetricsTest.java new file mode 100644 index 00000000..76be9286 --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/hbase/JMXMetricsTest.java @@ -0,0 +1,311 @@ +package com.alipay.oceanbase.hbase; + +import com.alipay.oceanbase.hbase.util.JMXMetricsTestHelper; +import com.alipay.oceanbase.hbase.util.MetricsExporter; +import com.alipay.oceanbase.hbase.util.OHMetrics; +import com.alipay.oceanbase.hbase.util.ObHTableTestUtil; +import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.*; + +import java.sql.Connection; +import java.sql.Statement; + +import static org.apache.hadoop.hbase.client.MetricsConnection.CLIENT_SIDE_METRICS_ENABLED_KEY; + +/** + * test JMX indicators registered in OHMetrics and OHMetricTracker + */ +public class JMXMetricsTest { + private static org.apache.hadoop.hbase.client.Connection connection; + private static Table hTable; + private static JMXMetricsTestHelper jmxHelper; + private static String metricsName; + private static OHMetrics metrics; + + @BeforeClass + public static void setUp() throws Exception { + jmxHelper = new JMXMetricsTestHelper(); + Configuration config = ObHTableTestUtil.newConfiguration(); + config.set(CLIENT_SIDE_METRICS_ENABLED_KEY, "true"); + connection = ConnectionFactory.createConnection(config); + hTable = connection.getTable(TableName.valueOf("test_multi_cf")); + if (hTable instanceof OHTable) { + metrics = ((OHTable) hTable).getMetrics(); + if (metrics != null) { + metricsName = metrics.getMetricsName(); + } + } + + if (metrics == null) { + System.out.println("Metrics not enabled, skipping JMX test"); + } + } + + @AfterClass + public static void cleanUp() throws Exception { + Connection conn = ObHTableTestUtil.getConnection(); + Statement statement = conn.createStatement(); + statement.execute("truncate table test_multi_cf$family_with_group1"); + statement.execute("truncate table test_multi_cf$family_with_group2"); + statement.execute("truncate table test_multi_cf$family_with_group3"); + statement.close(); + conn.close(); + hTable.close(); + connection.close(); + } + + /** + * test if all indicators have been registered to JMX + */ + @Test + public void testMetricsRegisteredInJMX() throws Exception { + if (metrics == null) { + Assert.assertTrue("for testing Metrics in JMX, set CLIENT_SIDE_METRICS_ENABLED_KEY", false); + } + // print all registered metrics items + jmxHelper.printAllOHMetricsMBeans(); + + for (OHOperationType type : OHOperationType.values()) { + if (type.getValue() == OHOperationType.INVALID.getValue()) { + continue; + } + jmxHelper.assertAllMetricsRegistered(type, metricsName); + } + } + + /** + * test PUT metrics in JMX + */ + @Test + public void testPutMetricsViaJMX() throws Exception { + if (metrics == null) { + Assert.assertTrue("for testing Metrics in JMX, set CLIENT_SIDE_METRICS_ENABLED_KEY", false); + } + + String row1 = "jmx_test_row1"; + String family1 = "family_with_group1"; + + JMXMetricsTestHelper.JMXMetricsInfo initialInfo = + jmxHelper.getMetricsInfo(OHOperationType.PUT, metricsName); + + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + + Thread.sleep(11000); + + JMXMetricsTestHelper.JMXMetricsInfo updatedInfo = + jmxHelper.getMetricsInfo(OHOperationType.PUT, metricsName); + + JMXChecker(OHOperationType.PUT, initialInfo, updatedInfo); + } + + /** + * test GET metrics in JMX + */ + @Test + public void testGetMetricsViaJMX() throws Exception { + if (metrics == null) { + Assert.assertTrue("for testing Metrics in JMX, set CLIENT_SIDE_METRICS_ENABLED_KEY", false); + } + + String row1 = "jmx_test_row2"; + String family1 = "family_with_group1"; + + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + + JMXMetricsTestHelper.JMXMetricsInfo initialInfo = + jmxHelper.getMetricsInfo(OHOperationType.GET, metricsName); + + Get get = new Get(Bytes.toBytes(row1)); + hTable.get(get); + + Thread.sleep(11000); + + JMXMetricsTestHelper.JMXMetricsInfo updatedInfo = + jmxHelper.getMetricsInfo(OHOperationType.GET, metricsName); + + JMXChecker(OHOperationType.GET, initialInfo, updatedInfo); + } + + /** + * test failure metrics in JMX + */ + @Test + public void testFailedOperationMetricsViaJMX() throws Exception { + if (metrics == null) { + Assert.assertTrue("for testing Metrics in JMX, set CLIENT_SIDE_METRICS_ENABLED_KEY", false); + } + + Long oldFailedOpCount = (Long) jmxHelper.getMeterAttribute(OHOperationType.PUT, metricsName, "Count"); + Double oldMeanFailRate = (Double) jmxHelper.getMeterAttribute(OHOperationType.PUT, metricsName, "MeanRate"); + Assert.assertTrue("Failed operation count should be 0", + oldFailedOpCount == 0); + Assert.assertTrue("Failed operation mean rate should be 0", + oldMeanFailRate == 0); + + try { + Put put = new Put(Bytes.toBytes("test_row")); + hTable.put(put); + } catch (Exception e) { + System.out.println("Expected exception: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("No columns")); + } + + Thread.sleep(11000); + + Long newFailedOpCount = (Long) jmxHelper.getMeterAttribute(OHOperationType.PUT, metricsName, "Count"); + Double newMeanFailRate = (Double) jmxHelper.getMeterAttribute(OHOperationType.PUT, metricsName, "MeanRate"); + + System.out.println("Failed operation - Initial fail count: " + oldFailedOpCount); + System.out.println("Failed operation - Updated fail count: " + newFailedOpCount); + + Assert.assertTrue("Failed operation count should increase", + newFailedOpCount > oldFailedOpCount); + Assert.assertTrue("Failed operation mean rate should be > 0", + newMeanFailRate > 0); + } + + /** + * test PUT latency histogram + */ + @Test + public void testJMXLatencyHistogramAccess() throws Exception { + if (metrics == null) { + Assert.assertTrue("for testing Metrics in JMX, set CLIENT_SIDE_METRICS_ENABLED_KEY", false); + } + + String row1 = "jmx_test_row3"; + String family1 = "family_with_group1"; + + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + + Thread.sleep(11000); + + Long count = (Long) jmxHelper.getTimerAttribute( + OHOperationType.PUT, metricsName, "Count"); + Double averageLatency = (Double) jmxHelper.getTimerAttribute( + OHOperationType.PUT, metricsName, "MeanRate"); + Double p99 = (Double) jmxHelper.getTimerAttribute( + OHOperationType.PUT, metricsName, "99thPercentile"); + + System.out.println("JMX Latency access:"); + System.out.println(" Count: " + count); + System.out.println(" MeanRate: " + averageLatency); + System.out.println(" 99thPercentile: " + p99); + + Assert.assertTrue("Count should be > 0", count > 0); + Assert.assertTrue("averageLatency should be > 0", averageLatency > 0); + Assert.assertTrue("99thPercentile should be > 0", p99 > 0); + } + + /** + * test PUT Counter indicator, totalSingleOpCount and totalRuntime + */ + @Test + public void testJMXCounterAccess() throws Exception { + if (metrics == null) { + Assert.assertTrue("for testing Metrics in JMX, set CLIENT_SIDE_METRICS_ENABLED_KEY", false); + } + + String row1 = "jmx_test_row4"; + String family1 = "family_with_group1"; + + Long initialSingleOpCount = (Long) jmxHelper.getCounterAttribute( + OHOperationType.PUT, metricsName, "totalSingleOpCount", "Count"); + Long initialRuntime = (Long) jmxHelper.getCounterAttribute( + OHOperationType.PUT, metricsName, "totalRuntime", "Count"); + + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + + Thread.sleep(11000); + + Long updatedSingleOpCount = (Long) jmxHelper.getCounterAttribute( + OHOperationType.PUT, metricsName, "totalSingleOpCount", "Count"); + Long updatedRuntime = (Long) jmxHelper.getCounterAttribute( + OHOperationType.PUT, metricsName, "totalRuntime", "Count"); + + System.out.println("Counter metrics:"); + System.out.println(" totalSingleOpCount: " + initialSingleOpCount + " -> " + updatedSingleOpCount); + System.out.println(" totalRuntime: " + initialRuntime + " -> " + updatedRuntime); + + Assert.assertTrue("totalSingleOpCount should increase", + updatedSingleOpCount > initialSingleOpCount); + Assert.assertTrue("totalRuntime should increase", + updatedRuntime > initialRuntime); + } + + /** + * test if the indicators acquired by MetricsExporter are the same with the ones from JMX + */ + @Test + public void testMetricsExporterVSJMX() throws Exception { + if (metrics == null) { + Assert.assertTrue("for testing Metrics in JMX, set CLIENT_SIDE_METRICS_ENABLED_KEY", false); + } + + String row1 = "jmx_test_row5"; + String family1 = "family_with_group1"; + + Put put = new Put(Bytes.toBytes(row1)); + put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + hTable.put(put); + + Thread.sleep(11000); + + MetricsExporter exporter = metrics.acquireMetrics(OHOperationType.PUT); + long exporterCount = exporter.getCount(); + double exporterMeanRate = exporter.getAverageOps(); + double exporterMean = exporter.getAverageLatency(); + double exporterP99 = exporter.get99thPercentile(); + long exporterFailCount = exporter.getFailCount(); + + JMXMetricsTestHelper.JMXMetricsInfo jmxInfo = + jmxHelper.getMetricsInfo(OHOperationType.PUT, metricsName); + + System.out.println("MetricsExporter vs JMX:"); + System.out.println(" Count: " + exporterCount + " vs " + jmxInfo.totalOpCount); + System.out.println(" MeanRate: " + exporterMeanRate + " vs " + jmxInfo.meanOps); + System.out.println(" Mean: " + exporterMean + " vs " + jmxInfo.meanLatency); + System.out.println(" 99thPercentile: " + exporterP99 + " vs " + jmxInfo.P99thPercentile); + System.out.println(" FailCount: " + exporterFailCount + " vs " + jmxInfo.failedOpCount); + + Assert.assertEquals("Count should match", exporterCount, jmxInfo.totalOpCount); + Assert.assertEquals("MeanRate should match", exporterMeanRate, jmxInfo.meanOps, 0.001); + Assert.assertEquals("Mean should match", exporterMean, jmxInfo.meanLatency, 0.001); + Assert.assertEquals("99thPercentile should match", exporterP99, jmxInfo.P99thPercentile, 0.001); + Assert.assertEquals("FailCount should match", exporterFailCount, jmxInfo.failedOpCount); + } + + private void JMXChecker(OHOperationType type, + JMXMetricsTestHelper.JMXMetricsInfo oldInfo, + JMXMetricsTestHelper.JMXMetricsInfo newInfo) throws Exception { + System.out.printf("%s Initial metrics: " + oldInfo + "%n", type.name()); + System.out.printf("%s Updated metrics: " + newInfo + "%n", type.name()); + + Assert.assertTrue(String.format("%s count should be greater", type.name()), + newInfo.totalOpCount > oldInfo.totalOpCount); + Assert.assertTrue(String.format("%s mean ops should be > 0", type.name()), + newInfo.meanOps >= oldInfo.meanOps); + Assert.assertTrue(String.format("%s mean latency should be > 0", type.name()), + newInfo.meanLatency > 0); + Assert.assertTrue(String.format("%s latency 99th percentile should be > 0", type.name()), + newInfo.P99thPercentile > 0); + Assert.assertTrue(String.format("%s total single op count should be greater", type.name()), + newInfo.totalSingleOpCount > oldInfo.totalSingleOpCount); + } +} + diff --git a/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java b/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java index 17e77f91..2046ea72 100644 --- a/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java +++ b/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java @@ -20,6 +20,7 @@ import java.util.concurrent.CountDownLatch; import static org.apache.hadoop.hbase.client.MetricsConnection.CLIENT_SIDE_METRICS_ENABLED_KEY; +import static org.junit.Assert.fail; public class OHMetricsTest { protected static Connection connection; @@ -91,46 +92,30 @@ public void testPutOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.PUT); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); + Put put = new Put(Bytes.toBytes(row1)); put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); put.addColumn(Bytes.toBytes(family2), Bytes.toBytes("q1"), Bytes.toBytes("val1")); put.addColumn(Bytes.toBytes(family3), Bytes.toBytes("q1"), Bytes.toBytes("val1")); hTable.put(put); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.PUT); - System.out.println("PUT - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("PUT - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("PUT - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("PUT - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("PUT - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("PUT - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("PUT - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.PUT, oldExporter, newExporter); + // test failed op metrics Assert.assertEquals(0L, newExporter.getFailCount()); try { put = new Put(Bytes.toBytes(row1)); hTable.put(put); + fail(); } catch (Exception e) { + System.out.println("PUT error: " + e.getMessage()); Assert.assertTrue(e.getMessage().contains("No columns")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.PUT); - System.out.println("PUT - FailCount: " + newExporter.getFailCount()); - Assert.assertEquals(1L, newExporter.getFailCount()); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.PUT); + failureMetricsChecker(OHOperationType.PUT, newExporter, failedExporter); } @Test @@ -142,49 +127,31 @@ public void testGetOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.GET); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put = new Put(Bytes.toBytes(row1)); put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); hTable.put(put); - Get get = new Get(Bytes.toBytes(row1)); hTable.get(get); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.GET); - System.out.println("GET - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("GET - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("GET - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("GET - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("GET - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("GET - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("GET - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.GET, oldExporter, newExporter); + // test failed op metrics Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.get(get); + fail(); } catch (Exception e) { System.out.println("GET error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.GET); - System.out.println("GET - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.GET); + failureMetricsChecker(OHOperationType.GET, newExporter, failedExporter); } @Test @@ -198,13 +165,6 @@ public void testGetListOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.GET_LIST); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put1 = new Put(Bytes.toBytes(row1)); @@ -216,41 +176,30 @@ public void testGetListOperationsMetrics() throws Exception { hTable.put(put1); hTable.put(put2); hTable.put(put3); - List gets = Arrays.asList( new Get(Bytes.toBytes(row1)), new Get(Bytes.toBytes(row2)), new Get(Bytes.toBytes(row3)) ); hTable.get(gets); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.GET_LIST); - System.out.println("GET_LIST - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("GET_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); - System.out.println("GET_LIST - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("GET_LIST - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("GET_LIST - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("GET_LIST - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("GET_LIST - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.GET_LIST, oldExporter, newExporter); + // test failed op metrics Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.get(gets); + fail(); } catch (Exception e) { System.out.println("GET_LIST error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.GET_LIST); - System.out.println("GET_LIST - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.GET_LIST); + failureMetricsChecker(OHOperationType.GET_LIST, newExporter, failedExporter); } @Test @@ -264,13 +213,6 @@ public void testPutListOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); List puts = new ArrayList<>(); Put put1 = new Put(Bytes.toBytes(row1)); @@ -282,25 +224,13 @@ public void testPutListOperationsMetrics() throws Exception { Put put3 = new Put(Bytes.toBytes(row3)); put3.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val3")); puts.add(put3); - hTable.put(puts); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); - System.out.println("PUT_LIST - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("PUT_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); - System.out.println("PUT_LIST - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("PUT_LIST - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("PUT_LIST - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("PUT_LIST - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("PUT_LIST - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.PUT_LIST, oldExporter, newExporter); + // test failed op metrics Assert.assertEquals(0L, newExporter.getFailCount()); try { put1 = new Put(Bytes.toBytes(row1)); @@ -309,13 +239,14 @@ public void testPutListOperationsMetrics() throws Exception { puts.add(put1); puts.add(put2); hTable.put(puts); + fail(); } catch (Exception e) { + System.out.println(e.getMessage()); Assert.assertTrue(e.getMessage().contains("No columns")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); - System.out.println("PUT_LIST - FailCount: " + newExporter.getFailCount()); - Assert.assertEquals(1L, newExporter.getFailCount()); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); + failureMetricsChecker(OHOperationType.PUT_LIST, newExporter, failedExporter); } @Test @@ -327,50 +258,32 @@ public void testDeleteOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.DELETE); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put = new Put(Bytes.toBytes(row1)); put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); hTable.put(put); - Delete delete = new Delete(Bytes.toBytes(row1)); delete.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1")); hTable.delete(delete); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.DELETE); - System.out.println("DELETE - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("DELETE - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("DELETE - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("DELETE - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("DELETE - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("DELETE - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("DELETE - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.DELETE, oldExporter, newExporter); + // test failed op metrics Assert.assertEquals(0L, newExporter.getFailCount()); try { - delete = new Delete((byte[]) null); - hTable.delete(delete); + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + notExistTable.delete(delete); + fail(); } catch (Exception e) { System.out.println("DELETE error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.DELETE); - System.out.println("DELETE - FailCount: " + newExporter.getFailCount()); - Assert.assertEquals(1L, newExporter.getFailCount()); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.DELETE); + failureMetricsChecker(OHOperationType.DELETE, newExporter, failedExporter); } @Test @@ -384,13 +297,6 @@ public void testDeleteListOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.DELETE_LIST); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put1 = new Put(Bytes.toBytes(row1)); @@ -402,7 +308,6 @@ public void testDeleteListOperationsMetrics() throws Exception { hTable.put(put1); hTable.put(put2); hTable.put(put3); - List deletes = new ArrayList<>(); Delete delete1 = new Delete(Bytes.toBytes(row1)); delete1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1")); @@ -413,37 +318,24 @@ public void testDeleteListOperationsMetrics() throws Exception { Delete delete3 = new Delete(Bytes.toBytes(row3)); delete3.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1")); deletes.add(delete3); - hTable.delete(deletes); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.DELETE_LIST); - System.out.println("DELETE_LIST - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("DELETE_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); - System.out.println("DELETE_LIST - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("DELETE_LIST - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("DELETE_LIST - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("DELETE_LIST - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("DELETE_LIST - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.DELETE_LIST, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.delete(deletes); + fail(); } catch (Exception e) { - e.printStackTrace(); System.out.println("DELETE_LIST error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.DELETE_LIST); - System.out.println("DELETE_LIST - FailCount: " + newExporter.getFailCount()); - Assert.assertEquals(1L, newExporter.getFailCount()); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.DELETE_LIST); + failureMetricsChecker(OHOperationType.DELETE_LIST, newExporter, failedExporter); } @Test @@ -455,51 +347,31 @@ public void testExistsOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.EXISTS); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put = new Put(Bytes.toBytes(row1)); put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); hTable.put(put); - Get get = new Get(Bytes.toBytes(row1)); boolean exists = hTable.exists(get); Assert.assertTrue(exists); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.EXISTS); - System.out.println("EXISTS - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("EXISTS - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("EXISTS - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("EXISTS - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("EXISTS - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("EXISTS - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("EXISTS - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.EXISTS, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); - get = new Get(Bytes.toBytes(row1)); notExistTable.exists(get); + fail(); } catch (Exception e) { System.out.println("EXISTS error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.EXISTS); - System.out.println("EXISTS - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.EXISTS); + failureMetricsChecker(OHOperationType.EXISTS, newExporter, failedExporter); } @Test @@ -513,13 +385,6 @@ public void testExistsListOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.EXISTS_LIST); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put1 = new Put(Bytes.toBytes(row1)); @@ -528,7 +393,6 @@ public void testExistsListOperationsMetrics() throws Exception { put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val2")); hTable.put(put1); hTable.put(put2); - List gets = Arrays.asList( new Get(Bytes.toBytes(row1)), new Get(Bytes.toBytes(row2)), @@ -538,34 +402,23 @@ public void testExistsListOperationsMetrics() throws Exception { Assert.assertTrue(exists[0]); Assert.assertTrue(exists[1]); Assert.assertFalse(exists[2]); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.EXISTS_LIST); - System.out.println("EXISTS_LIST - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("EXISTS_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); - System.out.println("EXISTS_LIST - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("EXISTS_LIST - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("EXISTS_LIST - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("EXISTS_LIST - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("EXISTS_LIST - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.EXISTS_LIST, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.exists(gets); + fail(); } catch (Exception e) { System.out.println("EXISTS_LIST error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.EXISTS_LIST); - System.out.println("EXISTS_LIST - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.EXISTS_LIST); + failureMetricsChecker(OHOperationType.EXISTS_LIST, newExporter, failedExporter); } @Test @@ -579,13 +432,6 @@ public void testScanOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.SCAN); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put1 = new Put(Bytes.toBytes(row1)); @@ -597,7 +443,6 @@ public void testScanOperationsMetrics() throws Exception { hTable.put(put1); hTable.put(put2); hTable.put(put3); - Scan scan = new Scan(); scan.addFamily(Bytes.toBytes(family1)); ResultScanner scanner = hTable.getScanner(scan); @@ -607,34 +452,23 @@ public void testScanOperationsMetrics() throws Exception { } scanner.close(); Assert.assertEquals(3, count); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.SCAN); - System.out.println("SCAN - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("SCAN - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("SCAN - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("SCAN - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("SCAN - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("SCAN - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("SCAN - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.SCAN, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.getScanner(scan); + fail(); } catch (Exception e) { System.out.println("SCAN error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.SCAN); - System.out.println("SCAN - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.SCAN); + failureMetricsChecker(OHOperationType.SCAN, newExporter, failedExporter); } @Test @@ -648,13 +482,6 @@ public void testBatchOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.BATCH); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); List actions = new ArrayList<>(); Put put1 = new Put(Bytes.toBytes(row1)); @@ -665,38 +492,26 @@ public void testBatchOperationsMetrics() throws Exception { actions.add(put2); Get get1 = new Get(Bytes.toBytes(row3)); actions.add(get1); - Object[] results = new Object[actions.size()]; hTable.batch(actions, results); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.BATCH); - System.out.println("BATCH - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("BATCH - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(3.0, newExporter.getAverageSingleOpCount(), 0.1); - System.out.println("BATCH - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("BATCH - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("BATCH - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("BATCH - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("BATCH - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.BATCH, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); results = new Object[actions.size()]; notExistTable.batch(actions, results); + fail(); } catch (Exception e) { System.out.println("BATCH error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.BATCH); - System.out.println("BATCH - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.BATCH); + failureMetricsChecker(OHOperationType.BATCH, newExporter, failedExporter); } @Test @@ -709,13 +524,6 @@ public void testBatchCallbackOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.BATCH_CALLBACK); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); List actions = new ArrayList<>(); Put put1 = new Put(Bytes.toBytes(row1)); @@ -724,7 +532,6 @@ public void testBatchCallbackOperationsMetrics() throws Exception { Put put2 = new Put(Bytes.toBytes(row2)); put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val2")); actions.add(put2); - Object[] results = new Object[actions.size()]; hTable.batchCallback(actions, results, new Batch.Callback() { @Override @@ -732,22 +539,10 @@ public void update(byte[] region, byte[] row, Result result) { // Do nothing } }); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.BATCH_CALLBACK); - System.out.println("BATCH_CALLBACK - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("BATCH_CALLBACK - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(2.0, newExporter.getAverageSingleOpCount(), 0.1); - System.out.println("BATCH_CALLBACK - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("BATCH_CALLBACK - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("BATCH_CALLBACK - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("BATCH_CALLBACK - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("BATCH_CALLBACK - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.BATCH_CALLBACK, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { @@ -759,13 +554,14 @@ public void update(byte[] region, byte[] row, Result result) { // Do nothing } }); + fail(); } catch (Exception e) { System.out.println("BATCH_CALLBACK error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.BATCH_CALLBACK); - System.out.println("BATCH_CALLBACK - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.BATCH_CALLBACK); + failureMetricsChecker(OHOperationType.BATCH_CALLBACK, newExporter, failedExporter); } @Test @@ -777,40 +573,20 @@ public void testCheckAndPutOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_PUT); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put1 = new Put(Bytes.toBytes(row1)); put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); hTable.put(put1); - Put put2 = new Put(Bytes.toBytes(row1)); put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q2"), Bytes.toBytes("val2")); boolean result = hTable.checkAndPut(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1"), put2); Assert.assertTrue(result); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_PUT); - System.out.println("CHECK_AND_PUT - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("CHECK_AND_PUT - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("CHECK_AND_PUT - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("CHECK_AND_PUT - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("CHECK_AND_PUT - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("CHECK_AND_PUT - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("CHECK_AND_PUT - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.CHECK_AND_PUT, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { @@ -819,13 +595,14 @@ public void testCheckAndPutOperationsMetrics() throws Exception { put3.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q3"), Bytes.toBytes("val3")); notExistTable.checkAndPut(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q2"), Bytes.toBytes("val2"), put3); + fail(); } catch (Exception e) { System.out.println("CHECK_AND_PUT error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_PUT); - System.out.println("CHECK_AND_PUT - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_PUT); + failureMetricsChecker(OHOperationType.CHECK_AND_PUT, newExporter, failedExporter); } @Test @@ -837,53 +614,34 @@ public void testCheckAndDeleteOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_DELETE); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put = new Put(Bytes.toBytes(row1)); put.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); hTable.put(put); - Delete delete = new Delete(Bytes.toBytes(row1)); delete.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q2")); boolean result = hTable.checkAndDelete(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1"), delete); Assert.assertTrue(result); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_DELETE); - System.out.println("CHECK_AND_DELETE - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("CHECK_AND_DELETE - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("CHECK_AND_DELETE - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("CHECK_AND_DELETE - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("CHECK_AND_DELETE - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("CHECK_AND_DELETE - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("CHECK_AND_DELETE - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.CHECK_AND_DELETE, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.checkAndDelete(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1"), delete); + fail(); } catch (Exception e) { System.out.println("CHECK_AND_DELETE error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_DELETE); - System.out.println("CHECK_AND_DELETE - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_DELETE); + failureMetricsChecker(OHOperationType.CHECK_AND_DELETE, newExporter, failedExporter); } @Test @@ -895,19 +653,11 @@ public void testCheckAndMutateOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_MUTATE); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); // Prepare data first Put put1 = new Put(Bytes.toBytes(row1)); put1.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); hTable.put(put1); - Put put2 = new Put(Bytes.toBytes(row1)); put2.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q2"), Bytes.toBytes("val2")); Delete delete = new Delete(Bytes.toBytes(row1)); @@ -915,39 +665,27 @@ public void testCheckAndMutateOperationsMetrics() throws Exception { RowMutations rowMutations = new RowMutations(Bytes.toBytes(row1)); rowMutations.add(put2); rowMutations.add(delete); - boolean result = hTable.checkAndMutate(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q1"), CompareFilter.CompareOp.EQUAL, Bytes.toBytes("val1"), rowMutations); Assert.assertTrue(result); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_MUTATE); - System.out.println("CHECK_AND_MUTATE - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("CHECK_AND_MUTATE - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(2.0, newExporter.getAverageSingleOpCount(), 0.1); - System.out.println("CHECK_AND_MUTATE - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("CHECK_AND_MUTATE - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("CHECK_AND_MUTATE - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("CHECK_AND_MUTATE - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("CHECK_AND_MUTATE - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.CHECK_AND_MUTATE, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.checkAndMutate(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q1"), CompareFilter.CompareOp.EQUAL, Bytes.toBytes("val1"), rowMutations); + fail(); } catch (Exception e) { System.out.println("CHECK_AND_MUTATE error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_MUTATE); - System.out.println("CHECK_AND_MUTATE - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_MUTATE); + failureMetricsChecker(OHOperationType.CHECK_AND_MUTATE, newExporter, failedExporter); } @Test @@ -959,45 +697,27 @@ public void testAppendOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.APPEND); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); Append append = new Append(Bytes.toBytes(row1)); - append.add(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); + append.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), Bytes.toBytes("val1")); hTable.append(append); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.APPEND); - System.out.println("APPEND - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("APPEND - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("APPEND - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("APPEND - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("APPEND - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("APPEND - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("APPEND - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.APPEND, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.append(append); + fail(); } catch (Exception e) { System.out.println("APPEND error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.APPEND); - System.out.println("APPEND - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.APPEND); + failureMetricsChecker(OHOperationType.APPEND, newExporter, failedExporter); } @Test @@ -1009,45 +729,27 @@ public void testIncrementOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.INCREMENT); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); Increment increment = new Increment(Bytes.toBytes(row1)); increment.addColumn(Bytes.toBytes(family1), Bytes.toBytes("q1"), 10L); hTable.increment(increment); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.INCREMENT); - System.out.println("APPEND - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("APPEND - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("APPEND - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("APPEND - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("APPEND - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("APPEND - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("APPEND - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.INCREMENT, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.increment(increment); + fail(); } catch (Exception e) { System.out.println("INCREMENT error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.INCREMENT); - System.out.println("INCREMENT - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.INCREMENT); + failureMetricsChecker(OHOperationType.INCREMENT, newExporter, failedExporter); } @Test @@ -1059,44 +761,26 @@ public void testIncrementColumnValueOperationsMetrics() throws Exception { throw new ObTableUnexpectedException("unexpected null metrics"); } MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.INCREMENT_COLUMN_VALUE); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMaxLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.getMinLatency(), 0.001); - Assert.assertEquals(0.0, oldExporter.get99thPercentile(), 0.001); - Assert.assertEquals(0, oldExporter.getTotalRuntime()); long res = hTable.incrementColumnValue(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q1"), 10L); Assert.assertEquals(10L, res); + Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.INCREMENT_COLUMN_VALUE); - System.out.println("INCREMENT_COLUMN_VALUE - AverageOps: " + newExporter.getAverageOps()); - Assert.assertTrue(newExporter.getAverageOps() > 0); - System.out.println("INCREMENT_COLUMN_VALUE - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(1.0, newExporter.getAverageSingleOpCount(), 0.001); - System.out.println("INCREMENT_COLUMN_VALUE - AverageLatency: " + newExporter.getAverageLatency()); - Assert.assertTrue(newExporter.getAverageLatency() > 0); - System.out.println("INCREMENT_COLUMN_VALUE - MaxLatency: " + newExporter.getMaxLatency()); - Assert.assertTrue(newExporter.getMaxLatency() > 0); - System.out.println("INCREMENT_COLUMN_VALUE - MinLatency: " + newExporter.getMinLatency()); - Assert.assertTrue(newExporter.getMinLatency() > 0); - System.out.println("INCREMENT_COLUMN_VALUE - 99thPercentile: " + newExporter.get99thPercentile()); - Assert.assertTrue(newExporter.get99thPercentile() > 0); - System.out.println("INCREMENT_COLUMN_VALUE - TotalRuntime: " + newExporter.getTotalRuntime()); - Assert.assertTrue(newExporter.getTotalRuntime() > 0); + metricsChecker(OHOperationType.INCREMENT_COLUMN_VALUE, oldExporter, newExporter); Assert.assertEquals(0L, newExporter.getFailCount()); try { Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); notExistTable.incrementColumnValue(Bytes.toBytes(row1), Bytes.toBytes(family1), Bytes.toBytes("q1"), 10L); + fail(); } catch (Exception e) { System.out.println("INCREMENT_COLUMN_VALUE error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); } Thread.sleep(11000); // sleep over 10 second - newExporter = metrics.acquireMetrics(OHOperationType.INCREMENT_COLUMN_VALUE); - System.out.println("INCREMENT_COLUMN_VALUE - FailCount: " + newExporter.getFailCount()); - Assert.assertTrue(newExporter.getFailCount() >= 0); + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.INCREMENT_COLUMN_VALUE); + failureMetricsChecker(OHOperationType.INCREMENT_COLUMN_VALUE, newExporter, failedExporter); } @Test @@ -1107,23 +791,19 @@ public void testEmptyBatchMetrics() throws Exception { } // test put list MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); List puts = new ArrayList<>(); hTable.put(puts); Thread.sleep(11000); // sleep over 10 second MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); - System.out.println("PUT_LIST - AverageOps: " + newExporter.getAverageOps()); // average ops will larger than 0 even if the list is empty // because the metrics of ops is recorded for put method + System.out.println("PUT_LIST - AverageOps: " + newExporter.getAverageOps()); Assert.assertTrue(newExporter.getAverageOps() > 0); System.out.println("PUT_LIST - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(0.0, newExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(oldExporter.getAverageSingleOpCount(), newExporter.getAverageSingleOpCount(), 0.001); // test batch oldExporter = metrics.acquireMetrics(OHOperationType.BATCH); - Assert.assertEquals(0.0, oldExporter.getAverageOps(), 0.001); - Assert.assertEquals(0.0, oldExporter.getAverageSingleOpCount(), 0.001); List actions = new ArrayList<>(); Object[] results = new Object[actions.size()]; hTable.batch(actions, results); @@ -1132,7 +812,7 @@ public void testEmptyBatchMetrics() throws Exception { System.out.println("BATCH - AverageOps: " + newExporter.getAverageOps()); Assert.assertTrue(newExporter.getAverageOps() > 0); System.out.println("BATCH - AverageSingleOpCount: " + newExporter.getAverageSingleOpCount()); - Assert.assertEquals(0.0, newExporter.getAverageSingleOpCount(), 0.001); + Assert.assertEquals(oldExporter.getAverageSingleOpCount(), newExporter.getAverageSingleOpCount(), 0.001); } @Test @@ -1145,6 +825,8 @@ public void testConcurrentOperationMetrics() throws Exception { int threadCount = 10; int operationsPerThread = 100; CountDownLatch latch = new CountDownLatch(threadCount); + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.PUT); + long oldCount = oldExporter.getCount(); for (int i = 0; i < threadCount; i++) { int threadId = i; @@ -1176,10 +858,54 @@ public void testConcurrentOperationMetrics() throws Exception { Assert.assertEquals(threadCount * operationsPerThread, cellCount); Thread.sleep(11000); - MetricsExporter exporter = metrics.acquireMetrics(OHOperationType.PUT); + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.PUT); + long realCount = newExporter.getCount() - oldCount; long expectedCount = threadCount * operationsPerThread; - System.out.println("exporter count: " + exporter.getCount()); + System.out.println("real count: " + realCount); System.out.println("expected count: " + expectedCount); - Assert.assertTrue(exporter.getCount() == expectedCount); + Assert.assertEquals(expectedCount, realCount); + } + + private void metricsChecker(OHOperationType type, + MetricsExporter oldExporter, + MetricsExporter newExporter) throws Exception { + System.out.println(String.format("%s - current AverageOps: " + newExporter.getAverageOps() + + ", before AverageOps: " + oldExporter.getAverageOps(), type.name())); + Assert.assertTrue(String.format("%s average ops should larger than before", type.name()), + newExporter.getAverageOps() > oldExporter.getAverageOps()); + + System.out.println(String.format("%s - current AverageSingleOpCount: " + newExporter.getAverageSingleOpCount() + + ", before AverageSingleOpCount: " + oldExporter.getAverageSingleOpCount(), type.name())); + Assert.assertTrue(String.format("%s average single op count should > 0", type.name()), + newExporter.getAverageSingleOpCount() > 0); + + System.out.println(String.format("%s - current AverageLatency: " + newExporter.getAverageLatency() + + ", before AverageLatency: " + oldExporter.getAverageLatency(), type.name())); + Assert.assertTrue(String.format("%s average latency should > 0", type.name()), + newExporter.getAverageLatency() > 0); + + System.out.println(String.format("%s - current MaxLatency: " + newExporter.getMaxLatency() + + ", before MaxLatency: " + oldExporter.getMaxLatency(), type.name())); + Assert.assertTrue(String.format("%s max latency should > 0", type.name()), + newExporter.getMaxLatency() > 0); + + System.out.println(String.format("%s - current 99thPercentile: " + newExporter.get99thPercentile() + + ", before 99thPercentile: " + oldExporter.get99thPercentile(), type.name())); + Assert.assertTrue(String.format("%s P99 latency should > 0", type.name()), + newExporter.get99thPercentile() > 0); + + System.out.println(String.format("%s - current TotalRuntime: " + newExporter.getTotalRuntime() + + ", before TotalRuntime: " + oldExporter.getTotalRuntime(), type.name())); + Assert.assertTrue(String.format("%s runtime should larger than before", type.name()), + newExporter.getTotalRuntime() > oldExporter.getTotalRuntime()); + } + + private void failureMetricsChecker(OHOperationType type, + MetricsExporter oldExporter, + MetricsExporter newExporter) throws Exception { + Assert.assertEquals(String.format("%s initial failed op count should be 0", type.name()), + 0L, oldExporter.getFailCount()); + Assert.assertEquals(String.format("%s failed op count should be 1", type.name()), + 1, newExporter.getFailCount()); } } diff --git a/src/test/java/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java b/src/test/java/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java new file mode 100644 index 00000000..36ed5702 --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java @@ -0,0 +1,233 @@ +package com.alipay.oceanbase.hbase.util; + +import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; +import org.junit.Assert; + +import javax.management.*; +import java.lang.management.ManagementFactory; +import java.util.Set; + +/** + * JMX Metrics Test Helper + */ +public class JMXMetricsTestHelper { + + private static final String JMX_DOMAIN = "com.oceanbase.hbase.metrics"; + private final MBeanServer mBeanServer; + + public JMXMetricsTestHelper() { + this.mBeanServer = ManagementFactory.getPlatformMBeanServer(); + } + + /** + * acquire the ObjectName of a specific attribute in JMX + * + * @param opType OHOperationType + * @param metricsName metrics name + * @param attributeName attribute name (latencyHistogram, failedOpCounter, totalSingleOpCount, totalRuntime) + * @return ObjectName + */ + public ObjectName getObjectName(OHOperationType opType, String metricsName, + String attributeName) { + try { + // the format of JMX name: domain:name=metricName + // example: com.alipay.oceanbase.hbase.util.OHMetrics.PUT.latencyHistogram.metricsName + String name = String.format("%s.%s.%s.%s", + OHMetrics.class.getName(), + opType.name(), + attributeName, + metricsName); + + String objectNameStr = String.format("%s:name=%s", + JMX_DOMAIN, name); + return new ObjectName(objectNameStr); + } catch (MalformedObjectNameException e) { + throw new RuntimeException("Failed to create ObjectName", e); + } + } + + /** + * acquire latency attribute + * Timer attributes: Count, MeanRate, OneMinuteRate, FiveMinuteRate, FifteenMinuteRate + * Min, Max, Mean, StdDev, 50thPercentile, 75thPercentile, 95thPercentile, 98thPercentile, 99thPercentile, 999thPercentile + */ + public Object getTimerAttribute(OHOperationType opType, String metricsName, String attributeName) { + try { + ObjectName objectName = getObjectName(opType, metricsName, "latencyHistogram"); + return mBeanServer.getAttribute(objectName, attributeName); + } catch (Exception e) { + throw new RuntimeException("Failed to get Timer attribute: " + attributeName, e); + } + } + + /** + * acquire failed operations attribute + * Meter attributes: Count, MeanRate, OneMinuteRate, FiveMinuteRate, FifteenMinuteRate + */ + public Object getMeterAttribute(OHOperationType opType, String metricsName, String attributeName) { + try { + ObjectName objectName = getObjectName(opType, metricsName, "failedOpCounter"); + return mBeanServer.getAttribute(objectName, attributeName); + } catch (Exception e) { + throw new RuntimeException("Failed to get Meter attribute: " + attributeName, e); + } + } + + /** + * acquire Counter attribute + * Counter attributes: Count + */ + public Object getCounterAttribute(OHOperationType opType, String metricsName, + String counterName, String attributeName) { + try { + ObjectName objectName = getObjectName(opType, metricsName, counterName); + return mBeanServer.getAttribute(objectName, attributeName); + } catch (Exception e) { + throw new RuntimeException("Failed to get Counter attribute: " + attributeName, e); + } + } + + /** + * verify latency attributes + */ + public void assertTimerExists(OHOperationType opType, String metricsName) { + try { + ObjectName objectName = getObjectName(opType, metricsName, "latencyHistogram"); + Assert.assertTrue("Timer should be registered in JMX", + mBeanServer.isRegistered(objectName)); + } catch (Exception e) { + Assert.fail("Failed to check Timer existence: " + e.getMessage()); + } + } + + /** + * verify failed operation attributes + */ + public void assertMeterExists(OHOperationType opType, String metricsName) { + try { + ObjectName objectName = getObjectName(opType, metricsName, "failedOpCounter"); + Assert.assertTrue("Meter should be registered in JMX", + mBeanServer.isRegistered(objectName)); + } catch (Exception e) { + Assert.fail("Failed to check Meter existence: " + e.getMessage()); + } + } + + /** + * verify Counter attributes + */ + public void assertCounterExists(OHOperationType opType, String metricsName, String counterName) { + try { + ObjectName objectName = getObjectName(opType, metricsName, counterName); + Assert.assertTrue("Counter should be registered in JMX", + mBeanServer.isRegistered(objectName)); + } catch (Exception e) { + Assert.fail("Failed to check Counter existence: " + e.getMessage()); + } + } + + /** + * get all registered OHMetrics MBean + */ + public Set getAllOHMetricsMBeans() { + try { + ObjectName pattern = new ObjectName(JMX_DOMAIN + ":*"); + return mBeanServer.queryNames(pattern, null); + } catch (MalformedObjectNameException e) { + throw new RuntimeException("Failed to query MBeans", e); + } + } + + /**) + * print all registered OHMetrics MBean info + */ + public void printAllOHMetricsMBeans() { + Set mBeans = getAllOHMetricsMBeans(); + System.out.println("=== All OHMetrics MBeans ==="); + for (ObjectName objectName : mBeans) { + System.out.println("MBean: " + objectName.getCanonicalName()); + try { + MBeanInfo mBeanInfo = mBeanServer.getMBeanInfo(objectName); + System.out.println(" Attributes:"); + for (MBeanAttributeInfo attr : mBeanInfo.getAttributes()) { + try { + Object value = mBeanServer.getAttribute(objectName, attr.getName()); + System.out.println(" " + attr.getName() + " = " + value); + } catch (Exception e) { + System.out.println(" " + attr.getName() + " = (error: " + e.getMessage() + ")"); + } + } + } catch (Exception e) { + System.out.println(" (Failed to get MBeanInfo: " + e.getMessage() + ")"); + } + System.out.println(); + } + } + + public void assertAllMetricsRegistered(OHOperationType opType, String metricsName) { + assertTimerExists(opType, metricsName); + assertMeterExists(opType, metricsName); + assertCounterExists(opType, metricsName, "totalSingleOpCount"); + assertCounterExists(opType, metricsName, "totalRuntime"); + } + + /** + * get the entire metrics info + */ + public JMXMetricsInfo getMetricsInfo(OHOperationType opType, String metricsName) { + JMXMetricsInfo info = new JMXMetricsInfo(); + + // latency histogram + info.totalOpCount = (Long) getTimerAttribute(opType, metricsName, "Count"); + info.meanOps = (Double) getTimerAttribute(opType, metricsName, "MeanRate"); + info.oneMinuteRateOps = (Double) getTimerAttribute(opType, metricsName, "OneMinuteRate"); + info.meanLatency = (Double) getTimerAttribute(opType, metricsName, "Mean"); + info.minLatency = (Double) getTimerAttribute(opType, metricsName, "Min"); + info.maxLatency = (Double) getTimerAttribute(opType, metricsName, "Max"); + info.P99thPercentile = (Double) getTimerAttribute(opType, metricsName, "99thPercentile"); + + // failure operations + info.failedOpCount = (Long) getMeterAttribute(opType, metricsName, "Count"); + info.meanFailRate = (Double) getMeterAttribute(opType, metricsName, "MeanRate"); + info.oneMinuteFailRate = (Double) getMeterAttribute(opType, metricsName, "OneMinuteRate"); + + // Counter + info.totalSingleOpCount = (Long) getCounterAttribute(opType, metricsName, "totalSingleOpCount", "Count"); + info.totalRuntime = (Long) getCounterAttribute(opType, metricsName, "totalRuntime", "Count"); + + return info; + } + + /** + * JMX info carrier + */ + public static class JMXMetricsInfo { + // Latency attributes + public long totalOpCount; + public double meanOps; + public double oneMinuteRateOps; + public double meanLatency; + public double minLatency; + public double maxLatency; + public double P99thPercentile; + + // Failure operation attributes + public long failedOpCount; + public double meanFailRate; + public double oneMinuteFailRate; + + // singleOpCount and totalRuntime + public long totalSingleOpCount; + public long totalRuntime; + + @Override + public String toString() { + return String.format( + "JMXMetricsInfo: { totalOpCount=%d, meanOps=%.2f, meanLatency=%.2f, " + + "P99thPercentile=%.2f, failedOpCount=%d, totalSingleOpCount=%d, totalRuntime=%d }", + totalOpCount, meanOps, meanLatency, P99thPercentile, + failedOpCount, totalSingleOpCount, totalRuntime); + } + } +} + From 0cf3a86f2be5e47369a3002f7cb77204aa3b61bd Mon Sep 17 00:00:00 2001 From: maochongxin Date: Tue, 11 Nov 2025 21:46:14 +0800 Subject: [PATCH 06/15] hotkey get opt case --- .../hbase/OHTableGetOptimizeTest.java | 1046 +++++++++++++++++ .../OHTableSecondaryPartGetOptimizeTest.java | 969 +++++++++++++++ src/test/java/unit_test_db.sql | 74 ++ 3 files changed, 2089 insertions(+) create mode 100644 src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java create mode 100644 src/test/java/com/alipay/oceanbase/hbase/secondary/OHTableSecondaryPartGetOptimizeTest.java diff --git a/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java b/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java new file mode 100644 index 00000000..518027a1 --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java @@ -0,0 +1,1046 @@ +/*- + * #%L + * OBKV HBase Client Framework + * %% + * Copyright (C) 2022 OceanBase Group + * %% + * OBKV HBase Client Framework is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * #L% + */ + +package com.alipay.oceanbase.hbase; + +import com.alipay.oceanbase.hbase.util.ObHTableTestUtil; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.*; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.*; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.*; + +import static org.apache.hadoop.hbase.util.Bytes.toBytes; + +/** + * Test class for HBase Get optimization with MaxVersions=1 on single-level partition tables + * This test ONLY runs Get optimization related test cases on single-level partition tables + * Each test case uses its own table instance with single-level partitioning + */ +public class OHTableGetOptimizeTest { + + private Table hTable; + + @BeforeClass + public static void setupClass() throws Exception { + // Initialize test environment if needed + } + + @After + public void tearDown() throws IOException { + if (hTable != null) { + hTable.close(); + } + } + + @AfterClass + public static void tearDownClass() throws SQLException { + ObHTableTestUtil.closeConn(); + } + + /** + * Test Get optimization on single-level partition table with MaxVersions=1 + * Verifies: Table-level MaxVersions=1 and setMaxVersions(1) both return only latest version + */ + @Test + public void testGetOptimizeWithMaxVersion1() throws Exception { + // Initialize table - single-level KEY partition with MaxVersions=1 + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize"); + + String family1 = "family_max_version_1"; + String family2 = "family_max_version_default"; + String key1 = "optimize_key_001"; + String key2 = "optimize_key_002"; + String column1 = "col1"; + String value1 = "value1"; + String value2 = "value2"; + String value3 = "value3"; + + // Scenario 1: Table with MaxVersions=1, single column specified + long t1 = System.currentTimeMillis(); + Put put = new Put(toBytes(key1)); + put.addColumn(family1.getBytes(), column1.getBytes(), t1, toBytes(value1)); + hTable.put(put); + + Thread.sleep(10); + long t2 = System.currentTimeMillis(); + put = new Put(toBytes(key1)); + put.addColumn(family1.getBytes(), column1.getBytes(), t2, toBytes(value2)); + hTable.put(put); + + Thread.sleep(10); + long t3 = System.currentTimeMillis(); + put = new Put(toBytes(key1)); + put.addColumn(family1.getBytes(), column1.getBytes(), t3, toBytes(value3)); + hTable.put(put); + + // Get with single column + Get get = new Get(toBytes(key1)); + get.addColumn(family1.getBytes(), column1.getBytes()); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + + // Test min(1, 10) = 1 + get = new Get(toBytes(key1)); + get.addColumn(family1.getBytes(), column1.getBytes()); + get.setMaxVersions(10); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + + // Get with setMaxVersions(1) + put = new Put(toBytes(key2)); + put.addColumn(family2.getBytes(), column1.getBytes(), t1, toBytes(value1)); + hTable.put(put); + + put = new Put(toBytes(key2)); + put.addColumn(family2.getBytes(), column1.getBytes(), t2, toBytes(value2)); + hTable.put(put); + + put = new Put(toBytes(key2)); + put.addColumn(family2.getBytes(), column1.getBytes(), t3, toBytes(value3)); + hTable.put(put); + + // Get with setMaxVersions(1) + get = new Get(toBytes(key2)); + get.addColumn(family2.getBytes(), column1.getBytes()); + get.setMaxVersions(1); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + + // Verify multiple versions exist when requesting them + get = new Get(toBytes(key2)); + get.addColumn(family2.getBytes(), column1.getBytes()); + get.setMaxVersions(10); + r = hTable.get(get); + Assert.assertTrue(r.rawCells().length >= 3); + Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(value2, Bytes.toString(r.rawCells()[1].getValueArray(), r.rawCells()[1].getValueOffset(), r.rawCells()[1].getValueLength())); + Assert.assertEquals(value1, Bytes.toString(r.rawCells()[2].getValueArray(), r.rawCells()[2].getValueOffset(), r.rawCells()[2].getValueLength())); + } + + /** + * Test Get optimization with single column query + * Verifies: Table MaxVersions=1 returns only latest version for single column + */ + @Test + public void testGetOptimizeSingleColumn() throws Exception { + // Initialize table - single-level KEY partition with MaxVersions=1 + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_t"); + + String family = "family_max_version_1"; + String key = "single_col_key"; + String col1 = "column1"; + long t1 = System.currentTimeMillis(); + + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("value_c1_v1")); + hTable.put(put); + + Thread.sleep(10); + long t2 = System.currentTimeMillis(); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("value_c1_v2")); + hTable.put(put); + + Thread.sleep(10); + long t3 = System.currentTimeMillis(); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("value_c1_v3")); + hTable.put(put); + + + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_c1_v3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + + + String col2 = "column2"; + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("value_c2_v1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("value_c2_v2")); + hTable.put(put); + + + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col2.getBytes()); + Result r2 = hTable.get(get); + Assert.assertEquals(1, r2.rawCells().length); + Assert.assertEquals("value_c2_v2", Bytes.toString(r2.rawCells()[0].getValueArray(), r2.rawCells()[0].getValueOffset(), r2.rawCells()[0].getValueLength())); + } + + /** + * Test Get optimization with batch Get operations + * Verifies: Batch Get correctly returns latest version for each key + */ + @Test + public void testGetOptimizeBatchGet() throws Exception { + // Initialize table - single-level KEY partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize"); + + String family = "family_max_version_1"; + List keys = Arrays.asList("batch_key_01", "batch_key_02", "batch_key_03"); + String column = "batch_col"; + long t1 = System.currentTimeMillis(); + + + for (String key : keys) { + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t1, toBytes(key + "_v1")); + hTable.put(put); + + Thread.sleep(5); + long t2 = System.currentTimeMillis(); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t2, toBytes(key + "_v2")); + hTable.put(put); + + Thread.sleep(5); + long t3 = System.currentTimeMillis(); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t3, toBytes(key + "_v3")); + hTable.put(put); + } + + // Batch Get + List gets = new ArrayList<>(); + for (String key : keys) { + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + gets.add(get); + } + + Result[] results = hTable.get(gets); + Assert.assertEquals(3, results.length); + for (int i = 0; i < results.length; i++) { + Result r = results[i]; + Assert.assertEquals(1, r.rawCells().length); + + Assert.assertEquals(keys.get(i) + "_v3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + } + } + + /** + * Test Get optimization with explicit setMaxVersions(1) + * Verifies: setMaxVersions(1) returns latest version, different values return multiple versions + */ + @Test + public void testGetOptimizeExplicitMaxVersion1() throws Exception { + // Initialize table - single-level KEY partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_t"); + + String family = "family_max_version_default"; + String key = "explicit_max_v1_key"; + String column = "col"; + long baseTime = System.currentTimeMillis(); + + + for (int i = 1; i <= 5; i++) { + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), baseTime + i, toBytes("value_" + i)); + hTable.put(put); + Thread.sleep(5); + } + + // Get with setMaxVersions(1) + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setMaxVersions(1); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + + // Get with setMaxVersions(3) + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setMaxVersions(3); + r = hTable.get(get); + Assert.assertEquals(3, r.rawCells().length); + Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals("value_4", Bytes.toString(r.rawCells()[1].getValueArray(), r.rawCells()[1].getValueOffset(), r.rawCells()[1].getValueLength())); + Assert.assertEquals("value_3", Bytes.toString(r.rawCells()[2].getValueArray(), r.rawCells()[2].getValueOffset(), r.rawCells()[2].getValueLength())); + + // Get with default MaxVersions + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + } + + /** + * Test Get optimization with time range filtering + * Verifies: Time range correctly filters results with MaxVersions=1 + */ + @Test + public void testGetOptimizeWithTimeRange() throws Exception { + // Initialize table - single-level KEY partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_t"); + + String family = "family_max_version_1"; + String key = "time_range_key"; + String column = "col"; + long t1 = System.currentTimeMillis(); + + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t1, toBytes("value_t1")); + hTable.put(put); + + Thread.sleep(100); + long t2 = System.currentTimeMillis(); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t2, toBytes("value_t2")); + hTable.put(put); + + Thread.sleep(100); + long t3 = System.currentTimeMillis(); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t3, toBytes("value_t3")); + hTable.put(put); + + // Get with time range (t1 to t2+1) + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeRange(t1, t2 + 1); + Result r = hTable.get(get); + // With MaxVersions=1 at table level, should get only 1 version + Assert.assertTrue(r.rawCells().length >= 1); + // The result should be within the time range + Assert.assertTrue(r.rawCells()[0].getTimestamp() >= t1 && r.rawCells()[0].getTimestamp() <= t2); + + // Get with time range (t1 to t3+1) and setMaxVersions(1) + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeRange(t1, t3 + 1); + get.setMaxVersions(1); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + } + + /** + * Test Get optimization with specific timestamp + * Verifies: setTimestamp accurately retrieves data at exact timestamp, handles non-existent timestamp + */ + @Test + public void testGetOptimizeWithSpecificTimestamp() throws Exception { + // Initialize table - single-level KEY partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize"); + + String family1 = "family_max_version_1"; + String family2 = "family_max_version_default"; + String key = "specific_ts_key"; + String column = "col"; + + long t1 = System.currentTimeMillis(); + Thread.sleep(10); + long t2 = System.currentTimeMillis(); + Thread.sleep(10); + long t3 = System.currentTimeMillis(); + + // Test with MaxVersions=1 table + Put put = new Put(toBytes(key)); + put.addColumn(family1.getBytes(), column.getBytes(), t1, toBytes("value_t1")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family1.getBytes(), column.getBytes(), t2, toBytes("value_t2")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family1.getBytes(), column.getBytes(), t3, toBytes("value_t3")); + hTable.put(put); + + // Get with specific timestamp t2 + Get get = new Get(toBytes(key)); + get.addColumn(family1.getBytes(), column.getBytes()); + get.setTimeStamp(t2); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t2", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t2, r.rawCells()[0].getTimestamp()); + + // Get with specific timestamp t1 + get = new Get(toBytes(key)); + get.addColumn(family1.getBytes(), column.getBytes()); + get.setTimeStamp(t1); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t1", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t1, r.rawCells()[0].getTimestamp()); + + // Get with specific timestamp t3 + get = new Get(toBytes(key)); + get.addColumn(family1.getBytes(), column.getBytes()); + get.setTimeStamp(t3); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + + // Test with default MaxVersions table + String key2 = "specific_ts_key2"; + put = new Put(toBytes(key2)); + put.addColumn(family2.getBytes(), column.getBytes(), t1, toBytes("value2_t1")); + hTable.put(put); + + put = new Put(toBytes(key2)); + put.addColumn(family2.getBytes(), column.getBytes(), t2, toBytes("value2_t2")); + hTable.put(put); + + put = new Put(toBytes(key2)); + put.addColumn(family2.getBytes(), column.getBytes(), t3, toBytes("value2_t3")); + hTable.put(put); + + // Get with setTimestamp + setMaxVersions(1) + get = new Get(toBytes(key2)); + get.addColumn(family2.getBytes(), column.getBytes()); + get.setTimeStamp(t2); + get.setMaxVersions(1); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value2_t2", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t2, r.rawCells()[0].getTimestamp()); + + // Get with non-existent timestamp + long nonExistentTs = t1 - 1000; + get = new Get(toBytes(key2)); + get.addColumn(family2.getBytes(), column.getBytes()); + get.setTimeStamp(nonExistentTs); + r = hTable.get(get); + Assert.assertEquals(0, r.rawCells().length); + } + + /** + * Test Get with specific timestamp across multiple versions + * Verifies: Retrieves exact version at specified timestamp, returns empty for non-existent timestamp + */ + @Test + public void testGetOptimizeWithSpecificTimestampMultiVersions() throws Exception { + // Initialize table - single-level KEY partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_t"); + + String family = "family_max_version_default"; + String key = "specific_ts_multi_key"; + String column = "col"; + + long baseTime = System.currentTimeMillis(); + long[] timestamps = new long[5]; + for (int i = 0; i < 5; i++) { + timestamps[i] = baseTime + (i * 100); + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), timestamps[i], toBytes("value_" + i)); + hTable.put(put); + Thread.sleep(5); + } + + // Get with specific timestamp + for (int i = 0; i < 5; i++) { + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(timestamps[i]); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_" + i, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(timestamps[i], r.rawCells()[0].getTimestamp()); + } + + // Get with setTimestamp + setMaxVersions(1) + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(timestamps[2]); + get.setMaxVersions(1); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_2", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(timestamps[2], r.rawCells()[0].getTimestamp()); + + // Get with non-existent timestamp between two versions + long betweenTs = timestamps[2] + 50; + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(betweenTs); + r = hTable.get(get); + Assert.assertEquals(0, r.rawCells().length); + } + + /** + * Test Get optimization with multiple qualifiers on MaxVersions=1 table + * Verifies: Each qualifier returns only latest version when table MaxVersions=1 + */ + @Test + public void testGetOptimizeWithMultipleQualifiersOnMaxVersion1Table() throws Exception { + // Initialize table - single-level KEY partition with MaxVersions=1 + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize"); + + String family = "family_max_version_1"; + String key = "multi_qual_key_001"; + String col1 = "qualifier1"; + String col2 = "qualifier2"; + String col3 = "qualifier3"; + + long t1 = System.currentTimeMillis(); + Thread.sleep(10); + long t2 = System.currentTimeMillis(); + Thread.sleep(10); + long t3 = System.currentTimeMillis(); + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("col1_v1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("col1_v2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("col1_v3")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("col2_v1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("col2_v2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col2.getBytes(), t3, toBytes("col2_v3")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("col3_v1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col3.getBytes(), t2, toBytes("col3_v2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col3.getBytes(), t3, toBytes("col3_v3")); + hTable.put(put); + + // Get with multiple qualifiers + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + get.addColumn(family.getBytes(), col2.getBytes()); + get.addColumn(family.getBytes(), col3.getBytes()); + Result r = hTable.get(get); + + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength()); + resultMap.put(qualifier, value); + } + + Assert.assertEquals("col1_v3", resultMap.get(col1)); + Assert.assertEquals("col2_v3", resultMap.get(col2)); + Assert.assertEquals("col3_v3", resultMap.get(col3)); + } + + /** + * Test Get optimization with multiple qualifiers and setMaxVersions(1) + * Verifies: setMaxVersions(1) returns latest version per qualifier, higher values return multiple versions + */ + @Test + public void testGetOptimizeWithMultipleQualifiersAndExplicitMaxVersion1() throws Exception { + // Initialize table - single-level KEY partition with default MaxVersions + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_t"); + + String family = "family_max_version_default"; + String key = "multi_qual_key_002"; + String col1 = "q1"; + String col2 = "q2"; + String col3 = "q3"; + String col4 = "q4"; + + long baseTime = System.currentTimeMillis(); + + // Insert 5 versions for each qualifier + for (int i = 1; i <= 5; i++) { + long ts = baseTime + (i * 10); + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), ts, toBytes("q1_value_" + i)); + put.addColumn(family.getBytes(), col2.getBytes(), ts, toBytes("q2_value_" + i)); + put.addColumn(family.getBytes(), col3.getBytes(), ts, toBytes("q3_value_" + i)); + put.addColumn(family.getBytes(), col4.getBytes(), ts, toBytes("q4_value_" + i)); + hTable.put(put); + Thread.sleep(5); + } + + // Get with setMaxVersions(1) + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + get.addColumn(family.getBytes(), col2.getBytes()); + get.addColumn(family.getBytes(), col3.getBytes()); + get.addColumn(family.getBytes(), col4.getBytes()); + get.setMaxVersions(1); + Result r = hTable.get(get); + + Assert.assertEquals(4, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength()); + resultMap.put(qualifier, value); + } + + Assert.assertEquals("q1_value_5", resultMap.get(col1)); + Assert.assertEquals("q2_value_5", resultMap.get(col2)); + Assert.assertEquals("q3_value_5", resultMap.get(col3)); + Assert.assertEquals("q4_value_5", resultMap.get(col4)); + + // Get with setMaxVersions(3) + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + get.addColumn(family.getBytes(), col2.getBytes()); + get.setMaxVersions(3); + r = hTable.get(get); + + Assert.assertEquals(6, r.rawCells().length); + } + + /** + * Test Get optimization with multiple qualifiers and specific timestamp + * Verifies: All qualifiers return data at specified timestamp + */ + @Test + public void testGetOptimizeWithMultipleQualifiersAndTimestamp() throws Exception { + // Initialize table - single-level KEY partition with MaxVersions=1 + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize"); + + String family = "family_max_version_1"; + String key = "multi_qual_ts_key"; + String col1 = "qualifier_a"; + String col2 = "qualifier_b"; + String col3 = "qualifier_c"; + + long t1 = System.currentTimeMillis(); + Thread.sleep(10); + long t2 = System.currentTimeMillis(); + Thread.sleep(10); + long t3 = System.currentTimeMillis(); + + // Insert data at different timestamps + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("a_t1")); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("b_t1")); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("c_t1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("a_t2")); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("b_t2")); + put.addColumn(family.getBytes(), col3.getBytes(), t2, toBytes("c_t2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("a_t3")); + put.addColumn(family.getBytes(), col2.getBytes(), t3, toBytes("b_t3")); + put.addColumn(family.getBytes(), col3.getBytes(), t3, toBytes("c_t3")); + hTable.put(put); + + // Get with multiple qualifiers and specific timestamp + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + get.addColumn(family.getBytes(), col2.getBytes()); + get.addColumn(family.getBytes(), col3.getBytes()); + get.setTimeStamp(t2); + Result r = hTable.get(get); + + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength()); + long timestamp = r.rawCells()[i].getTimestamp(); + resultMap.put(qualifier, value); + Assert.assertEquals(t2, timestamp); + } + + Assert.assertEquals("a_t2", resultMap.get(col1)); + Assert.assertEquals("b_t2", resultMap.get(col2)); + Assert.assertEquals("c_t2", resultMap.get(col3)); + } + + /** + * Test min(table_max_version, setMaxVersions) rule with single qualifier + * Verifies: Returns min(1, N) = 1 version when table MaxVersions=1 regardless of client request + */ + @Test + public void testTableMaxVersionPrecedence() throws Exception { + // Initialize table - single-level KEY partition with MaxVersions=1 + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize"); + + String family = "family_max_version_1"; + String key = "precedence_test_key"; + String col = "test_col"; + + long baseTime = System.currentTimeMillis(); + + + for (int i = 1; i <= 5; i++) { + long ts = baseTime + (i * 10); + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col.getBytes(), ts, toBytes("value_" + i)); + hTable.put(put); + Thread.sleep(5); + } + + // Test: min(1, 3) = 1 + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col.getBytes()); + get.setMaxVersions(3); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + + // Test: min(1, 5) = 1 + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col.getBytes()); + get.setMaxVersions(5); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + + // Test: min(1, 10) = 1 + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col.getBytes()); + get.setMaxVersions(10); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + } + + /** + * Test min(table_max_version, setMaxVersions) rule with multiple qualifiers + * Verifies: Each qualifier returns min(1, 3) = 1 version independently + */ + @Test + public void testTableMaxVersionPrecedenceWithMultipleQualifiers() throws Exception { + // Initialize table - single-level KEY partition with MaxVersions=1 + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize"); + + String family = "family_max_version_1"; + String key = "multi_qual_precedence_key"; + String col1 = "q1"; + String col2 = "q2"; + String col3 = "q3"; + + long baseTime = System.currentTimeMillis(); + + for (int i = 1; i <= 5; i++) { + long ts = baseTime + (i * 10); + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), ts, toBytes("q1_v" + i)); + put.addColumn(family.getBytes(), col2.getBytes(), ts, toBytes("q2_v" + i)); + put.addColumn(family.getBytes(), col3.getBytes(), ts, toBytes("q3_v" + i)); + hTable.put(put); + Thread.sleep(5); + } + + // Test: min(1, 3) = 1 per qualifier + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + get.addColumn(family.getBytes(), col2.getBytes()); + get.addColumn(family.getBytes(), col3.getBytes()); + get.setMaxVersions(3); + Result r = hTable.get(get); + + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength()); + resultMap.put(qualifier, value); + } + + Assert.assertEquals("q1_v5", resultMap.get(col1)); + Assert.assertEquals("q2_v5", resultMap.get(col2)); + Assert.assertEquals("q3_v5", resultMap.get(col3)); + } + + /** + * Test Get without specifying qualifiers on MaxVersions=1 table + * Verifies: Returns latest version of all qualifiers + */ + @Test + public void testGetOptimizeWithoutQualifierOnMaxVersion1Table() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize"); + + String family = "family_max_version_1"; + String key = "no_qualifier_max_v1_key"; + String col1 = "qualifier_1"; + String col2 = "qualifier_2"; + String col3 = "qualifier_3"; + + long baseTime = System.currentTimeMillis(); + + for (int i = 1; i <= 5; i++) { + long ts = baseTime + (i * 10); + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), ts, toBytes("col1_value_" + i)); + put.addColumn(family.getBytes(), col2.getBytes(), ts, toBytes("col2_value_" + i)); + put.addColumn(family.getBytes(), col3.getBytes(), ts, toBytes("col3_value_" + i)); + hTable.put(put); + Thread.sleep(5); + } + + Get get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + Result r = hTable.get(get); + + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength()); + resultMap.put(qualifier, value); + } + + Assert.assertEquals("col1_value_5", resultMap.get(col1)); + Assert.assertEquals("col2_value_5", resultMap.get(col2)); + Assert.assertEquals("col3_value_5", resultMap.get(col3)); + } + + /** + * Test Get without specifying qualifiers with setMaxVersions(1) + * Verifies: Returns latest version of all qualifiers when setMaxVersions(1) + */ + @Test + public void testGetOptimizeWithoutQualifierAndExplicitMaxVersion1() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_t"); + + String family = "family_max_version_default"; + String key = "no_qualifier_explicit_v1_key"; + String col1 = "q1"; + String col2 = "q2"; + String col3 = "q3"; + String col4 = "q4"; + + long baseTime = System.currentTimeMillis(); + + for (int i = 1; i <= 5; i++) { + long ts = baseTime + (i * 10); + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), ts, toBytes("q1_v" + i)); + put.addColumn(family.getBytes(), col2.getBytes(), ts, toBytes("q2_v" + i)); + put.addColumn(family.getBytes(), col3.getBytes(), ts, toBytes("q3_v" + i)); + put.addColumn(family.getBytes(), col4.getBytes(), ts, toBytes("q4_v" + i)); + hTable.put(put); + Thread.sleep(5); + } + + Get get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + get.setMaxVersions(1); + Result r = hTable.get(get); + + Assert.assertEquals(4, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength()); + resultMap.put(qualifier, value); + } + + Assert.assertEquals("q1_v5", resultMap.get(col1)); + Assert.assertEquals("q2_v5", resultMap.get(col2)); + Assert.assertEquals("q3_v5", resultMap.get(col3)); + Assert.assertEquals("q4_v5", resultMap.get(col4)); + } + + /** + * Test Get without specifying qualifiers with specific timestamp + * Verifies: All qualifiers return data at specified timestamp + */ + @Test + public void testGetOptimizeWithoutQualifierAndTimestamp() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize"); + + String family = "family_max_version_1"; + String key = "no_qualifier_ts_key"; + String col1 = "qa"; + String col2 = "qb"; + String col3 = "qc"; + + long t1 = System.currentTimeMillis(); + Thread.sleep(10); + long t2 = System.currentTimeMillis(); + Thread.sleep(10); + long t3 = System.currentTimeMillis(); + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("qa_t1")); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("qb_t1")); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("qc_t1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("qa_t2")); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("qb_t2")); + put.addColumn(family.getBytes(), col3.getBytes(), t2, toBytes("qc_t2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("qa_t3")); + put.addColumn(family.getBytes(), col2.getBytes(), t3, toBytes("qb_t3")); + put.addColumn(family.getBytes(), col3.getBytes(), t3, toBytes("qc_t3")); + hTable.put(put); + + Get get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + get.setTimeStamp(t2); + Result r = hTable.get(get); + + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength()); + long timestamp = r.rawCells()[i].getTimestamp(); + resultMap.put(qualifier, value); + Assert.assertEquals(t2, timestamp); + } + + Assert.assertEquals("qa_t2", resultMap.get(col1)); + Assert.assertEquals("qb_t2", resultMap.get(col2)); + Assert.assertEquals("qc_t2", resultMap.get(col3)); + } + + /** + * Test Get without specifying qualifiers with timestamp and setMaxVersions(1) + * Verifies: Returns exact version at specified timestamp for all qualifiers + */ + @Test + public void testGetOptimizeWithoutQualifierTimestampAndMaxVersion1() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_t"); + + String family = "family_max_version_default"; + String key = "no_qualifier_ts_v1_key"; + String col1 = "qualifier_a"; + String col2 = "qualifier_b"; + String col3 = "qualifier_c"; + String col4 = "qualifier_d"; + + long baseTime = System.currentTimeMillis(); + long[] timestamps = new long[5]; + for (int i = 0; i < 5; i++) { + timestamps[i] = baseTime + (i * 100); + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), timestamps[i], toBytes("a_v" + i)); + put.addColumn(family.getBytes(), col2.getBytes(), timestamps[i], toBytes("b_v" + i)); + put.addColumn(family.getBytes(), col3.getBytes(), timestamps[i], toBytes("c_v" + i)); + put.addColumn(family.getBytes(), col4.getBytes(), timestamps[i], toBytes("d_v" + i)); + hTable.put(put); + Thread.sleep(5); + } + + Get get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + get.setTimeStamp(timestamps[2]); + get.setMaxVersions(1); + Result r = hTable.get(get); + + Assert.assertEquals(4, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength()); + long timestamp = r.rawCells()[i].getTimestamp(); + resultMap.put(qualifier, value); + Assert.assertEquals(timestamps[2], timestamp); + } + + Assert.assertEquals("a_v2", resultMap.get(col1)); + Assert.assertEquals("b_v2", resultMap.get(col2)); + Assert.assertEquals("c_v2", resultMap.get(col3)); + Assert.assertEquals("d_v2", resultMap.get(col4)); + + // Verify another timestamp + get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + get.setTimeStamp(timestamps[4]); + get.setMaxVersions(1); + Result r2 = hTable.get(get); + + Assert.assertEquals(4, r2.rawCells().length); + for (int i = 0; i < r2.rawCells().length; i++) { + Assert.assertEquals(timestamps[4], r2.rawCells()[i].getTimestamp()); + } + } +} diff --git a/src/test/java/com/alipay/oceanbase/hbase/secondary/OHTableSecondaryPartGetOptimizeTest.java b/src/test/java/com/alipay/oceanbase/hbase/secondary/OHTableSecondaryPartGetOptimizeTest.java new file mode 100644 index 00000000..d858980a --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/hbase/secondary/OHTableSecondaryPartGetOptimizeTest.java @@ -0,0 +1,969 @@ +/*- + * #%L + * OBKV HBase Client Framework + * %% + * Copyright (C) 2022 OceanBase Group + * %% + * OBKV HBase Client Framework is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * #L% + */ + +package com.alipay.oceanbase.hbase.secondary; + +import com.alipay.oceanbase.hbase.OHTable; +import com.alipay.oceanbase.hbase.util.ObHTableTestUtil; +import com.alipay.oceanbase.hbase.util.TableTemplateManager; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.*; +import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.*; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.*; + +import static org.apache.hadoop.hbase.util.Bytes.toBytes; +import static com.alipay.oceanbase.hbase.util.ObHTableSecondaryPartUtil.*; + +/** + * Test class for HBase Get optimization with MaxVersions=1 on secondary partition tables + * This test ONLY runs Get optimization related test cases on secondary partition tables + * Each test case uses its own table instance with secondary partitioning + */ +public class OHTableSecondaryPartGetOptimizeTest { + + private Table hTable; + + @BeforeClass + public static void before() throws Exception { + openDistributedExecute(); + } + + @AfterClass + public static void finish() throws Exception { + closeDistributedExecute(); + } + + @After + public void tearDown() throws IOException { + if (hTable != null) { + hTable.close(); + } + } + + @AfterClass + public static void tearDownClass() throws SQLException { + ObHTableTestUtil.closeConn(); + } + + /** + * Test Get optimization on KEY-RANGE secondary partition with single column + * Verifies: Get returns only latest version across all secondary partitions when MaxVersions defaults to 1 + */ + @Test + public void testGetOptimizeWithMaxVersion1OnKeyRange() throws Exception { + // Initialize table - KEY-RANGE secondary partition with default MaxVersions + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_key_range"); + + String family1 = "family1"; + String key1 = "sec_optimize_key_001"; + String column1 = "col1"; + String value1 = "value1"; + String value2 = "value2"; + String value3 = "value3"; + + // Data distributed across partitions: p1(t1=50), p2(t2=150), p3(t3=250) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + + Put put = new Put(toBytes(key1)); + put.addColumn(family1.getBytes(), column1.getBytes(), t1, toBytes(value1)); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key1)); + put.addColumn(family1.getBytes(), column1.getBytes(), t2, toBytes(value2)); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key1)); + put.addColumn(family1.getBytes(), column1.getBytes(), t3, toBytes(value3)); + hTable.put(put); + + // Get with default MaxVersions=1 + Get get = new Get(toBytes(key1)); + get.addColumn(family1.getBytes(), column1.getBytes()); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + } + + /** + * Test Get optimization on RANGE-KEY secondary partition with single column + * Verifies: Get returns only latest version across all partitions when MaxVersions defaults to 1 + */ + @Test + public void testGetOptimizeSingleColumnOnRangeKey() throws Exception { + // Initialize table - RANGE-KEY secondary partition with default MaxVersions + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_range_key"); + + String family = "family1"; + String key = "sec_single_col_key"; + String col1 = "column1"; + + // Data distributed across partitions: p1(t1=50), p2(t2=150), p3(t3=250) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("value_c1_v1")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("value_c1_v2")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("value_c1_v3")); + hTable.put(put); + + // Get single column + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_c1_v3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + + + String col2 = "column2"; + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("value_c2_v1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("value_c2_v2")); + hTable.put(put); + + + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col2.getBytes()); + Result r2 = hTable.get(get); + Assert.assertEquals(1, r2.rawCells().length); + Assert.assertEquals("value_c2_v2", Bytes.toString(r2.rawCells()[0].getValueArray(), r2.rawCells()[0].getValueOffset(), r2.rawCells()[0].getValueLength())); + } + + /** + * Test Get optimization with batch Get operations on KEY-RANGE secondary partition + * Verifies: Batch Get correctly returns latest version for each key across partitions + */ + @Test + public void testGetOptimizeBatchGetOnKeyRange() throws Exception { + // Initialize table - KEY-RANGE secondary partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_key_range"); + + String family = "family1"; + List keys = Arrays.asList("sec_batch_key_01", "sec_batch_key_02", "sec_batch_key_03"); + String column = "batch_col"; + + // Data distributed across partitions: p1(t1=50), p2(t2=150), p3(t3=250) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + + + for (String key : keys) { + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t1, toBytes(key + "_v1")); + hTable.put(put); + + Thread.sleep(5); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t2, toBytes(key + "_v2")); + hTable.put(put); + + Thread.sleep(5); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t3, toBytes(key + "_v3")); + hTable.put(put); + } + + // Batch Get + List gets = new ArrayList<>(); + for (String key : keys) { + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + gets.add(get); + } + + Result[] results = hTable.get(gets); + Assert.assertEquals(3, results.length); + for (int i = 0; i < results.length; i++) { + Result r = results[i]; + Assert.assertEquals(1, r.rawCells().length); + + Assert.assertEquals(keys.get(i) + "_v3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + } + } + + /** + * Test Get optimization with time range on KEY-RANGE secondary partition + * Verifies: Time range filtering correctly returns latest version within specified range across partitions + */ + @Test + public void testGetOptimizeWithTimeRangeOnKeyRange() throws Exception { + // Initialize table - KEY-RANGE secondary partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_key_range"); + + String family = "family1"; + String key = "sec_time_range_key"; + String column = "col"; + + // Data distributed across partitions: p1(t1=50), p2(t2=150), p3(t3=250) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t1, toBytes("value_t1")); + hTable.put(put); + + Thread.sleep(100); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t2, toBytes("value_t2")); + hTable.put(put); + + Thread.sleep(100); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t3, toBytes("value_t3")); + hTable.put(put); + + // Get with time range (t1 to t2+1) + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeRange(t1, t2 + 1); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + + Assert.assertEquals("value_t2", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t2, r.rawCells()[0].getTimestamp()); + + // Get with time range (t1 to t3+1) + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeRange(t1, t3 + 1); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + } + + /** + * Test Get optimization with specific timestamp on KEY-RANGE secondary partition + * Verifies: Get with setTimestamp accurately retrieves data from correct partition, handles non-existent timestamp + */ + @Test + public void testGetOptimizeWithSpecificTimestampOnKeyRange() throws Exception { + // Initialize table - KEY-RANGE secondary partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_key_range"); + + String family = "family1"; + String key = "sec_specific_ts_key"; + String column = "col"; + + // Data distributed across partitions: p1(t1=50), p2(t2=150), p3(t3=250) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t1, toBytes("value_t1")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t2, toBytes("value_t2")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t3, toBytes("value_t3")); + hTable.put(put); + + // Get with specific timestamp t2 + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(t2); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t2", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t2, r.rawCells()[0].getTimestamp()); + + // Get with specific timestamp t1 + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(t1); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t1", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t1, r.rawCells()[0].getTimestamp()); + + // Get with specific timestamp t3 + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(t3); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + + // Get with non-existent timestamp (between t1 and t2) + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(100L); + r = hTable.get(get); + Assert.assertEquals(0, r.rawCells().length); + } + + /** + * Test Get optimization with specific timestamp on RANGE-KEY secondary partition + * Verifies: Timestamp-based retrieval works correctly across different primary partitions + */ + @Test + public void testGetOptimizeWithSpecificTimestampOnRangeKey() throws Exception { + // Initialize table - RANGE-KEY secondary partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_range_key"); + + String family = "family1"; + String key = "sec_rk_specific_ts_key"; + String column = "col"; + + // Data distributed: p1(t1=50, t4=80), p2(t2=150, t5=180), p3(t3=250) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + long t4 = 80L; + long t5 = 180L; + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t1, toBytes("value_t1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t2, toBytes("value_t2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t3, toBytes("value_t3")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t4, toBytes("value_t4")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), column.getBytes(), t5, toBytes("value_t5")); + hTable.put(put); + + // Get with timestamp t3 from p3 + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(t3); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + + // Get with timestamp t5 from p2 + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(t5); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t5, r.rawCells()[0].getTimestamp()); + + // Get with timestamp t4 from p1 + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), column.getBytes()); + get.setTimeStamp(t4); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_t4", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t4, r.rawCells()[0].getTimestamp()); + } + + /** + * Test Get optimization with multiple qualifiers on KEY-RANGE secondary partition + * Verifies: Multiple qualifiers each return only latest version across partitions when MaxVersions defaults to 1 + */ + @Test + public void testGetOptimizeWithMultipleQualifiersOnKeyRange() throws Exception { + // Initialize table - KEY-RANGE secondary partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_key_range"); + + String family = "family1"; + String key = "multi_qual_key_001"; + String col1 = "qualifier1"; + String col2 = "qualifier2"; + String col3 = "qualifier3"; + + // Data distributed across partitions: p1(t1=50), p2(t2=150), p3(t3=250) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("col1_v1")); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("col2_v1")); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("col3_v1")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("col1_v2")); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("col2_v2")); + put.addColumn(family.getBytes(), col3.getBytes(), t2, toBytes("col3_v2")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("col1_v3")); + put.addColumn(family.getBytes(), col2.getBytes(), t3, toBytes("col2_v3")); + put.addColumn(family.getBytes(), col3.getBytes(), t3, toBytes("col3_v3")); + hTable.put(put); + + // Get with multiple qualifiers + default MaxVersions=1 - should trigger optimization + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + get.addColumn(family.getBytes(), col2.getBytes()); + get.addColumn(family.getBytes(), col3.getBytes()); + Result r = hTable.get(get); + + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[i].getValueOffset(), r.rawCells()[i].getValueLength()); + resultMap.put(qualifier, value); + } + + Assert.assertEquals("col1_v3", resultMap.get(col1)); + Assert.assertEquals("col2_v3", resultMap.get(col2)); + Assert.assertEquals("col3_v3", resultMap.get(col3)); + } + + /** + * Test Get optimization with multiple qualifiers on RANGE-KEY secondary partition + * Verifies: Multiple qualifiers each return only latest version across all partitions + */ + @Test + public void testGetOptimizeWithMultipleQualifiersOnRangeKey() throws Exception { + // Initialize table - RANGE-KEY secondary partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_range_key"); + + String family = "family1"; + String key = "rk_multi_qual_key_001"; + String col1 = "q1"; + String col2 = "q2"; + String col3 = "q3"; + String col4 = "q4"; + + // Data distributed: p1(t1=50, t4=80), p2(t2=150, t5=180), p3(t3=250) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + long t4 = 80L; + long t5 = 180L; + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("q1_value_1")); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("q2_value_1")); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("q3_value_1")); + put.addColumn(family.getBytes(), col4.getBytes(), t1, toBytes("q4_value_1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("q1_value_2")); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("q2_value_2")); + put.addColumn(family.getBytes(), col3.getBytes(), t2, toBytes("q3_value_2")); + put.addColumn(family.getBytes(), col4.getBytes(), t2, toBytes("q4_value_2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("q1_value_3")); + put.addColumn(family.getBytes(), col2.getBytes(), t3, toBytes("q2_value_3")); + put.addColumn(family.getBytes(), col3.getBytes(), t3, toBytes("q3_value_3")); + put.addColumn(family.getBytes(), col4.getBytes(), t3, toBytes("q4_value_3")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t4, toBytes("q1_value_4")); + put.addColumn(family.getBytes(), col2.getBytes(), t4, toBytes("q2_value_4")); + put.addColumn(family.getBytes(), col3.getBytes(), t4, toBytes("q3_value_4")); + put.addColumn(family.getBytes(), col4.getBytes(), t4, toBytes("q4_value_4")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t5, toBytes("q1_value_5")); + put.addColumn(family.getBytes(), col2.getBytes(), t5, toBytes("q2_value_5")); + put.addColumn(family.getBytes(), col3.getBytes(), t5, toBytes("q3_value_5")); + put.addColumn(family.getBytes(), col4.getBytes(), t5, toBytes("q4_value_5")); + hTable.put(put); + + // Get with multiple qualifiers + default MaxVersions=1 - should trigger optimization + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + get.addColumn(family.getBytes(), col2.getBytes()); + get.addColumn(family.getBytes(), col3.getBytes()); + get.addColumn(family.getBytes(), col4.getBytes()); + Result r = hTable.get(get); + + Assert.assertEquals(4, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[i].getValueOffset(), r.rawCells()[i].getValueLength()); + resultMap.put(qualifier, value); + } + + Assert.assertEquals("q1_value_3", resultMap.get(col1)); + Assert.assertEquals("q2_value_3", resultMap.get(col2)); + Assert.assertEquals("q3_value_3", resultMap.get(col3)); + Assert.assertEquals("q4_value_3", resultMap.get(col4)); + } + + /** + * Test Get optimization with multiple qualifiers and specific timestamp on KEY-RANGE secondary partition + * Verifies: All qualifiers return data at specified timestamp from correct partition + */ + @Test + public void testGetOptimizeWithMultipleQualifiersAndTimestamp() throws Exception { + // Initialize table - KEY-RANGE secondary partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_key_range"); + + String family = "family1"; + String key = "multi_qual_ts_key"; + String col1 = "qualifier_a"; + String col2 = "qualifier_b"; + String col3 = "qualifier_c"; + + // Data distributed across partitions: p1(t1=50), p2(t2=150), p3(t3=250) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("a_t1")); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("b_t1")); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("c_t1")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("a_t2")); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("b_t2")); + put.addColumn(family.getBytes(), col3.getBytes(), t2, toBytes("c_t2")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("a_t3")); + put.addColumn(family.getBytes(), col2.getBytes(), t3, toBytes("b_t3")); + put.addColumn(family.getBytes(), col3.getBytes(), t3, toBytes("c_t3")); + hTable.put(put); + + // Get with multiple qualifiers and specific timestamp t2 + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + get.addColumn(family.getBytes(), col2.getBytes()); + get.addColumn(family.getBytes(), col3.getBytes()); + get.setTimeStamp(t2); + Result r = hTable.get(get); + + System.out.println(Arrays.toString(r.rawCells())); + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[i].getValueOffset(), r.rawCells()[i].getValueLength()); + long timestamp = r.rawCells()[i].getTimestamp(); + resultMap.put(qualifier, value); + Assert.assertEquals(t2, timestamp); + } + + Assert.assertEquals("a_t2", resultMap.get(col1)); + Assert.assertEquals("b_t2", resultMap.get(col2)); + Assert.assertEquals("c_t2", resultMap.get(col3)); + } + + /** + * Test Get optimization with multiple qualifiers across different partitions + * Verifies: Get correctly retrieves latest version for each qualifier when they reside in different partitions + */ + @Test + public void testGetOptimizeWithMultipleQualifiersAcrossPartitions() throws Exception { + // Initialize table - KEY-RANGE secondary partition + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_key_range"); + + String family = "family1"; + String key = "multi_qual_cross_partition_key"; + String col1 = "qualifier_1"; + String col2 = "qualifier_2"; + String col3 = "qualifier_3"; + + // Each qualifier's latest in different partition: col1→p3(t3=250), col2→p1(t4=70), col3→p2(t5=180) + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + long t4 = 70L; + long t5 = 180L; + long t6 = 280L; + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("col1_p1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("col1_p2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("col1_p3")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("col2_p1_v1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col2.getBytes(), t4, toBytes("col2_p1_v2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("col3_p1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col3.getBytes(), t5, toBytes("col3_p2")); + hTable.put(put); + + // Get with multiple qualifiers + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col1.getBytes()); + get.addColumn(family.getBytes(), col2.getBytes()); + get.addColumn(family.getBytes(), col3.getBytes()); + Result r = hTable.get(get); + + System.out.println(Arrays.toString(r.rawCells())); + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + Map timestampMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[i].getValueOffset(), r.rawCells()[i].getValueLength()); + long timestamp = r.rawCells()[i].getTimestamp(); + resultMap.put(qualifier, value); + timestampMap.put(qualifier, timestamp); + } + + Assert.assertEquals("col1_p3", resultMap.get(col1)); + Assert.assertEquals(t3, (long) timestampMap.get(col1)); + + Assert.assertEquals("col2_p1_v2", resultMap.get(col2)); + Assert.assertEquals(t4, (long) timestampMap.get(col2)); + + Assert.assertEquals("col3_p2", resultMap.get(col3)); + Assert.assertEquals(t5, (long) timestampMap.get(col3)); + } + + /** + * Test Get without specifying qualifiers on KEY-RANGE secondary partition + * Verifies: Returns latest version of all qualifiers across partitions + */ + @Test + public void testGetOptimizeWithoutQualifierOnKeyRange() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_key_range"); + + String family = "family1"; + String key = "no_qualifier_key_range_001"; + String col1 = "qualifier_1"; + String col2 = "qualifier_2"; + String col3 = "qualifier_3"; + + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + + for (int i = 1; i <= 3; i++) { + long ts = (i == 1) ? t1 : (i == 2) ? t2 : t3; + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), ts, toBytes("col1_value_" + i)); + put.addColumn(family.getBytes(), col2.getBytes(), ts, toBytes("col2_value_" + i)); + put.addColumn(family.getBytes(), col3.getBytes(), ts, toBytes("col3_value_" + i)); + hTable.put(put); + Thread.sleep(5); + } + + Get get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + Result r = hTable.get(get); + + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[i].getValueOffset(), r.rawCells()[i].getValueLength()); + resultMap.put(qualifier, value); + } + + Assert.assertEquals("col1_value_3", resultMap.get(col1)); + Assert.assertEquals("col2_value_3", resultMap.get(col2)); + Assert.assertEquals("col3_value_3", resultMap.get(col3)); + } + + /** + * Test Get without specifying qualifiers on RANGE-KEY secondary partition + * Verifies: Returns latest version of all qualifiers across partitions + */ + @Test + public void testGetOptimizeWithoutQualifierOnRangeKey() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_range_key"); + + String family = "family1"; + String key = "no_qualifier_range_key_001"; + String col1 = "q1"; + String col2 = "q2"; + String col3 = "q3"; + String col4 = "q4"; + + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + long t4 = 80L; + long t5 = 180L; + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("q1_v1")); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("q2_v1")); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("q3_v1")); + put.addColumn(family.getBytes(), col4.getBytes(), t1, toBytes("q4_v1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("q1_v2")); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("q2_v2")); + put.addColumn(family.getBytes(), col3.getBytes(), t2, toBytes("q3_v2")); + put.addColumn(family.getBytes(), col4.getBytes(), t2, toBytes("q4_v2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("q1_v3")); + put.addColumn(family.getBytes(), col2.getBytes(), t3, toBytes("q2_v3")); + put.addColumn(family.getBytes(), col3.getBytes(), t3, toBytes("q3_v3")); + put.addColumn(family.getBytes(), col4.getBytes(), t3, toBytes("q4_v3")); + hTable.put(put); + + Get get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + Result r = hTable.get(get); + + Assert.assertEquals(4, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[i].getValueOffset(), r.rawCells()[i].getValueLength()); + resultMap.put(qualifier, value); + } + + Assert.assertEquals("q1_v3", resultMap.get(col1)); + Assert.assertEquals("q2_v3", resultMap.get(col2)); + Assert.assertEquals("q3_v3", resultMap.get(col3)); + Assert.assertEquals("q4_v3", resultMap.get(col4)); + } + + /** + * Test Get without specifying qualifiers with specific timestamp on KEY-RANGE + * Verifies: All qualifiers return data at specified timestamp across partitions + */ + @Test + public void testGetOptimizeWithoutQualifierAndTimestampOnKeyRange() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_key_range"); + + String family = "family1"; + String key = "no_qualifier_ts_key_range"; + String col1 = "qa"; + String col2 = "qb"; + String col3 = "qc"; + + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("qa_t1")); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("qb_t1")); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("qc_t1")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("qa_t2")); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("qb_t2")); + put.addColumn(family.getBytes(), col3.getBytes(), t2, toBytes("qc_t2")); + hTable.put(put); + + Thread.sleep(10); + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("qa_t3")); + put.addColumn(family.getBytes(), col2.getBytes(), t3, toBytes("qb_t3")); + put.addColumn(family.getBytes(), col3.getBytes(), t3, toBytes("qc_t3")); + hTable.put(put); + + Get get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + get.setTimeStamp(t2); + Result r = hTable.get(get); + + Assert.assertEquals(3, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[i].getValueOffset(), r.rawCells()[i].getValueLength()); + long timestamp = r.rawCells()[i].getTimestamp(); + resultMap.put(qualifier, value); + Assert.assertEquals(t2, timestamp); + } + + Assert.assertEquals("qa_t2", resultMap.get(col1)); + Assert.assertEquals("qb_t2", resultMap.get(col2)); + Assert.assertEquals("qc_t2", resultMap.get(col3)); + } + + /** + * Test Get without specifying qualifiers with specific timestamp on RANGE-KEY + * Verifies: All qualifiers return data at specified timestamp across different partitions + */ + @Test + public void testGetOptimizeWithoutQualifierAndTimestampOnRangeKey() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + hTable = new OHTable(c, "test_get_optimize_secondary_range_key"); + + String family = "family1"; + String key = "no_qualifier_ts_range_key"; + String col1 = "qualifier_a"; + String col2 = "qualifier_b"; + String col3 = "qualifier_c"; + String col4 = "qualifier_d"; + + long t1 = 50L; + long t2 = 150L; + long t3 = 250L; + long t4 = 80L; + long t5 = 180L; + + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t1, toBytes("a_v1")); + put.addColumn(family.getBytes(), col2.getBytes(), t1, toBytes("b_v1")); + put.addColumn(family.getBytes(), col3.getBytes(), t1, toBytes("c_v1")); + put.addColumn(family.getBytes(), col4.getBytes(), t1, toBytes("d_v1")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t2, toBytes("a_v2")); + put.addColumn(family.getBytes(), col2.getBytes(), t2, toBytes("b_v2")); + put.addColumn(family.getBytes(), col3.getBytes(), t2, toBytes("c_v2")); + put.addColumn(family.getBytes(), col4.getBytes(), t2, toBytes("d_v2")); + hTable.put(put); + + put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col1.getBytes(), t3, toBytes("a_v3")); + put.addColumn(family.getBytes(), col2.getBytes(), t3, toBytes("b_v3")); + put.addColumn(family.getBytes(), col3.getBytes(), t3, toBytes("c_v3")); + put.addColumn(family.getBytes(), col4.getBytes(), t3, toBytes("d_v3")); + hTable.put(put); + + Get get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + get.setTimeStamp(t2); + Result r = hTable.get(get); + + Assert.assertEquals(4, r.rawCells().length); + + Map resultMap = new HashMap<>(); + for (int i = 0; i < r.rawCells().length; i++) { + String qualifier = Bytes.toString(r.rawCells()[i].getQualifierArray(), r.rawCells()[i].getQualifierOffset(), r.rawCells()[i].getQualifierLength()); + String value = Bytes.toString(r.rawCells()[i].getValueArray(), r.rawCells()[i].getValueOffset(), r.rawCells()[i].getValueLength()); + long timestamp = r.rawCells()[i].getTimestamp(); + resultMap.put(qualifier, value); + Assert.assertEquals(t2, timestamp); + } + + Assert.assertEquals("a_v2", resultMap.get(col1)); + Assert.assertEquals("b_v2", resultMap.get(col2)); + Assert.assertEquals("c_v2", resultMap.get(col3)); + Assert.assertEquals("d_v2", resultMap.get(col4)); + + // Verify another timestamp + get = new Get(toBytes(key)); + get.addFamily(family.getBytes()); + get.setTimeStamp(t3); + Result r2 = hTable.get(get); + + Assert.assertEquals(4, r2.rawCells().length); + for (int i = 0; i < r2.rawCells().length; i++) { + Assert.assertEquals(t3, r2.rawCells()[i].getTimestamp()); + } + } +} diff --git a/src/test/java/unit_test_db.sql b/src/test/java/unit_test_db.sql index 3fd00d75..c6b3fb61 100644 --- a/src/test/java/unit_test_db.sql +++ b/src/test/java/unit_test_db.sql @@ -382,3 +382,77 @@ SUBPARTITION BY KEY(`K`) SUBPARTITIONS 3 PARTITION `p2` VALUES LESS THAN (200), PARTITION `p3` VALUES LESS THAN MAXVALUE ); + +use test; + +CREATE TABLEGROUP test_get_optimize_secondary_key_range SHARDING = 'ADAPTIVE'; +CREATE TABLE IF NOT EXISTS `test_get_optimize_secondary_key_range$family1` ( + `K` varbinary(1024) NOT NULL, + `Q` varbinary(256) NOT NULL, + `T` bigint(20) NOT NULL, + `V` varbinary(1024) DEFAULT NULL, + `G` bigint(20) GENERATED ALWAYS AS (ABS(T)), + PRIMARY KEY (`K`, `Q`, `T`) +) TABLEGROUP = test_get_optimize_secondary_key_range +PARTITION BY KEY(`K`) PARTITIONS 3 +SUBPARTITION BY RANGE COLUMNS(`G`) SUBPARTITION TEMPLATE ( + SUBPARTITION `p1` VALUES LESS THAN (100), + SUBPARTITION `p2` VALUES LESS THAN (200), + SUBPARTITION `p3` VALUES LESS THAN MAXVALUE +); + +CREATE TABLEGROUP test_get_optimize_secondary_range_key SHARDING = 'ADAPTIVE'; +CREATE TABLE IF NOT EXISTS `test_get_optimize_secondary_range_key$family1` ( + `K` varbinary(1024) NOT NULL, + `Q` varbinary(256) NOT NULL, + `T` bigint(20) NOT NULL, + `V` varbinary(1024) DEFAULT NULL, + `G` bigint(20) GENERATED ALWAYS AS (ABS(T)), + PRIMARY KEY (`K`, `Q`, `T`) +) TABLEGROUP = test_get_optimize_secondary_range_key +PARTITION BY RANGE COLUMNS(`G`) +SUBPARTITION BY KEY(`K`) SUBPARTITIONS 3 +( PARTITION `p1` VALUES LESS THAN (100), + PARTITION `p2` VALUES LESS THAN (200), + PARTITION `p3` VALUES LESS THAN MAXVALUE +); + +CREATE TABLEGROUP test_get_optimize SHARDING = 'ADAPTIVE'; +CREATE TABLE `test_get_optimize$family_max_version_1` ( + `K` varbinary(1024) NOT NULL, + `Q` varbinary(256) NOT NULL, + `T` bigint(20) NOT NULL, + `V` varbinary(1024) DEFAULT NULL, + PRIMARY KEY (`K`, `Q`, `T`) +) TABLEGROUP = test_get_optimize +KV_ATTRIBUTES ='{"Hbase": {"MaxVersions": 1}}' +PARTITION BY KEY(`K`) PARTITIONS 3; + +CREATE TABLE `test_get_optimize$family_max_version_default` ( + `K` varbinary(1024) NOT NULL, + `Q` varbinary(256) NOT NULL, + `T` bigint(20) NOT NULL, + `V` varbinary(1024) DEFAULT NULL, + PRIMARY KEY (`K`, `Q`, `T`) +) TABLEGROUP = test_get_optimize +PARTITION BY KEY(`K`) PARTITIONS 3; + +CREATE TABLEGROUP test_get_optimize_t SHARDING = 'ADAPTIVE'; +CREATE TABLE `test_get_optimize_t$family_max_version_1` ( + `K` varbinary(1024) NOT NULL, + `Q` varbinary(256) NOT NULL, + `T` bigint(20) NOT NULL, + `V` varbinary(1024) DEFAULT NULL, + PRIMARY KEY (`K`, `Q`, `T`) +) TABLEGROUP = test_get_optimize_t +KV_ATTRIBUTES ='{"Hbase": {"MaxVersions": 1}}' +PARTITION BY KEY(`K`) PARTITIONS 3; + +CREATE TABLE `test_get_optimize_t$family_max_version_default` ( + `K` varbinary(1024) NOT NULL, + `Q` varbinary(256) NOT NULL, + `T` bigint(20) NOT NULL, + `V` varbinary(1024) DEFAULT NULL, + PRIMARY KEY (`K`, `Q`, `T`) +) TABLEGROUP = test_get_optimize_t +PARTITION BY KEY(`K`) PARTITIONS 3; From 1fce32d9c56851aec4f93c5b26e54d4512d16c53 Mon Sep 17 00:00:00 2001 From: WeiXinChan Date: Mon, 17 Nov 2025 17:21:17 +0800 Subject: [PATCH 07/15] support weak read --- .../com/alipay/oceanbase/hbase/OHTable.java | 53 +- .../hbase/constants/OHConstants.java | 15 + .../hbase/util/OHConnectionConfiguration.java | 18 + .../hbase/util/ObTableClientManager.java | 10 + .../oceanbase/hbase/ObTableWeakReadTest.java | 2730 +++++++++++++++++ 5 files changed, 2818 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index 5efbb7f7..3c0db74a 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -972,7 +972,7 @@ public Result call() throws IOException { processColumnFilters(columnFilters, get.getFamilyMap()); obTableQuery = buildObTableQuery(get, columnFilters); ObTableQueryAsyncRequest request = buildObTableQueryAsyncRequest(obTableQuery, - getTargetTableName(tableNameString)); + getTargetTableName(tableNameString), isWeakRead(get)); ObTableClientQueryAsyncStreamResult clientQueryStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient .execute(request); @@ -995,7 +995,7 @@ public Result call() throws IOException { obTableQuery = buildObTableQuery(get, entry.getValue()); ObTableQueryRequest request = buildObTableQueryRequest(obTableQuery, getTargetTableName(tableNameString, Bytes.toString(family), - configuration)); + configuration), isWeakRead(get)); ObTableClientQueryStreamResult clientQueryStreamResult = (ObTableClientQueryStreamResult) obTableClient .execute(request); getMaxRowFromResult(clientQueryStreamResult, keyValueList, false, @@ -1069,7 +1069,7 @@ public ResultScanner call() throws IOException { obTableQuery = buildObTableQuery(filter, scan); request = buildObTableQueryAsyncRequest(obTableQuery, - getTargetTableName(tableNameString)); + getTargetTableName(tableNameString), isWeakRead(scan)); clientQueryAsyncStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient .execute(request); return new ClientStreamScanner(clientQueryAsyncStreamResult, @@ -1096,7 +1096,7 @@ public ResultScanner call() throws IOException { request = buildObTableQueryAsyncRequest( obTableQuery, getTargetTableName(tableNameString, Bytes.toString(family), - configuration)); + configuration), isWeakRead(scan)); clientQueryAsyncStreamResult = (ObTableClientQueryAsyncStreamResult) obTableClient .execute(request); return new ClientStreamScanner(clientQueryAsyncStreamResult, @@ -1151,7 +1151,7 @@ public List call() throws IOException { obTableQuery = buildObTableQuery(filter, scan); request = buildObTableQueryAsyncRequest(obTableQuery, - getTargetTableName(tableNameString)); + getTargetTableName(tableNameString), isWeakRead(scan)); request.setNeedTabletId(false); request.setAllowDistributeScan(false); String phyTableName = obTableClient.getPhyTableNameFromTableGroup( @@ -1188,7 +1188,7 @@ public List call() throws IOException { String targetTableName = getTargetTableName(tableNameString, Bytes.toString(family), configuration); - request = buildObTableQueryAsyncRequest(obTableQuery, targetTableName); + request = buildObTableQueryAsyncRequest(obTableQuery, targetTableName, isWeakRead(scan)); request.setNeedTabletId(false); request.setAllowDistributeScan(false); List partitions = obTableClient @@ -2041,6 +2041,32 @@ private ObTableQuery buildObTableQuery(final Get get, Collection columnQ return obTableQuery; } + /** + * Check if the Get or Scan operation is configured for weak read. + * + * @param query the Get or Scan object to check + * @return true if weak read is enabled, false otherwise + */ + public static boolean isWeakRead(Object query) { + if (query == null) { + return false; + } + byte[] consistency = null; + if (query instanceof Get) { + consistency = ((Get) query).getAttribute(HBASE_HTABLE_READ_CONSISTENCY); + } else if (query instanceof Scan) { + consistency = ((Scan) query).getAttribute(HBASE_HTABLE_READ_CONSISTENCY); + } else { + return false; + } + if (consistency == null) { + return false; + } + String consistencyStr = Bytes.toString(consistency); + System.out.println("consistencyStr: " + consistencyStr); + return "weak".equalsIgnoreCase(consistencyStr); + } + public static ObTableBatchOperation buildObTableBatchOperation(List rowList, List qualifiers) { ObTableBatchOperation batch = new ObTableBatchOperation(); @@ -2301,6 +2327,9 @@ private BatchOperation buildBatchOperation(String tableName, List ObTableClientQueryImpl query = new ObTableClientQueryImpl(tableName, obTableQuery, obTableClient); try { query.setRowKey(row(colVal("K", Bytes.toString(get.getRow())), colVal("Q", null), colVal("T", Integer.MAX_VALUE))); + if (isWeakRead(get)) { + query.setReadConsistency("weak"); + } } catch (Exception e) { logger.error("unexpected error occurs when set row key", e); throw new IOException(e); @@ -2475,18 +2504,23 @@ public static ObTableOperation buildObTableOperation(Cell kv, } private ObTableQueryRequest buildObTableQueryRequest(ObTableQuery obTableQuery, - String targetTableName) { + String targetTableName, + Boolean isWeakRead) { ObTableQueryRequest request = new ObTableQueryRequest(); request.setEntityType(ObTableEntityType.HKV); request.setTableQuery(obTableQuery); request.setTableName(targetTableName); + if (isWeakRead) { + request.setConsistencyLevel(ObTableConsistencyLevel.EVENTUAL); + } request.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); request.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); return request; } private ObTableQueryAsyncRequest buildObTableQueryAsyncRequest(ObTableQuery obTableQuery, - String targetTableName) { + String targetTableName, + Boolean isWeakRead) { ObTableQueryRequest request = new ObTableQueryRequest(); request.setEntityType(ObTableEntityType.HKV); request.setTableQuery(obTableQuery); @@ -2497,6 +2531,9 @@ private ObTableQueryAsyncRequest buildObTableQueryAsyncRequest(ObTableQuery obTa asyncRequest.setObTableQueryRequest(request); asyncRequest.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); asyncRequest.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); + if (isWeakRead) { + asyncRequest.setConsistencyLevel(ObTableConsistencyLevel.EVENTUAL); + } return asyncRequest; } diff --git a/src/main/java/com/alipay/oceanbase/hbase/constants/OHConstants.java b/src/main/java/com/alipay/oceanbase/hbase/constants/OHConstants.java index deb94f75..8260e2fe 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/constants/OHConstants.java +++ b/src/main/java/com/alipay/oceanbase/hbase/constants/OHConstants.java @@ -129,6 +129,21 @@ public final class OHConstants { */ public static final String HBASE_HTABLE_QUERY_HOT_ONLY = "hbase.htable.query.hot_only"; + /** + * use to specify the read consistency when performing a query. + */ + public static final String HBASE_HTABLE_READ_CONSISTENCY = "hbase.htable.read.consistency"; + + /** + * use to specify the idc when performing a query. + */ + public static final String HBASE_HTABLE_CLIENT_IDC = "hbase.htable.client.idc"; + + /** + * use to specify the route policy when performing a query. + */ + public static final String HBASE_HTABLE_CLIENT_ROUTE_POLICY = "hbase.htable.client.route.policy"; + /*-------------------------------------------------------------------------------------------------------------*/ /** diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHConnectionConfiguration.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHConnectionConfiguration.java index d2474a11..8c4830c3 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHConnectionConfiguration.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHConnectionConfiguration.java @@ -60,6 +60,9 @@ public class OHConnectionConfiguration { private final long writeBufferPeriodicFlushTimeoutMs; private final long writeBufferPeriodicFlushTimerTickMs; private final int numRetries; + private String globalWeakRead = null; + private String idc = null; + private String routePolicy = null; public OHConnectionConfiguration(Configuration conf) { this.paramUrl = conf.get(HBASE_OCEANBASE_PARAM_URL); @@ -111,6 +114,9 @@ public OHConnectionConfiguration(Configuration conf) { this.scannerMaxResultSize = conf.getLong( HConstants.HBASE_CLIENT_SCANNER_MAX_RESULT_SIZE_KEY, WRITE_BUFFER_SIZE_DEFAULT); this.maxKeyValueSize = conf.getInt(MAX_KEYVALUE_SIZE_KEY, MAX_KEYVALUE_SIZE_DEFAULT); + this.idc = conf.get(HBASE_HTABLE_CLIENT_IDC); + this.routePolicy = conf.get(HBASE_HTABLE_CLIENT_ROUTE_POLICY); + this.globalWeakRead = conf.get(HBASE_HTABLE_READ_CONSISTENCY); properties = new Properties(); for (Property property : Property.values()) { String value = conf.get(property.getKey()); @@ -223,4 +229,16 @@ public long getWriteBufferPeriodicFlushTimerTickMs() { public int getNumRetries() { return this.numRetries; } + + public String getIdc() { + return this.idc; + } + + public String getRoutePolicy() { + return this.routePolicy; + } + + public String getGlobalWeakRead() { + return this.globalWeakRead; + } } diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java b/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java index a68e0a30..7cccc9b0 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java @@ -19,6 +19,7 @@ import com.alipay.oceanbase.rpc.ObTableClient; import com.alipay.oceanbase.rpc.constant.Constants; +import com.alipay.oceanbase.rpc.location.model.ObRoutePolicy; import com.alipay.oceanbase.hbase.OHTable; import com.google.common.base.Objects; import org.apache.hadoop.classification.InterfaceAudience; @@ -118,6 +119,15 @@ public static ObTableClient getOrCreateObTableClient(ObTableClientKey obTableCli obTableClient.setPassword(obTableClientKey.getPassword()); obTableClient.setRpcConnectTimeout(connectionConfig.getRpcConnectTimeout()); obTableClient.addProperty(RPC_OPERATION_TIMEOUT.getKey(), Integer.toString(connectionConfig.getServerOperationTimeout())); + if (connectionConfig.getIdc() != null) { + obTableClient.setCurrentIDC(connectionConfig.getIdc()); + } + if (connectionConfig.getRoutePolicy() != null) { + obTableClient.setRoutePolicy(ObRoutePolicy.getByName(connectionConfig.getRoutePolicy())); + } + if (connectionConfig.getGlobalWeakRead() != null) { + obTableClient.setReadConsistency(connectionConfig.getGlobalWeakRead());; + } obTableClient.init(); OB_TABLE_CLIENT_INSTANCE.put(obTableClientKey, obTableClient); } diff --git a/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java b/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java new file mode 100644 index 00000000..a02423a6 --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java @@ -0,0 +1,2730 @@ +/*- + * #%L + * com.oceanbase:obkv-table-client + * %% + * Copyright (C) 2021 - 2025 OceanBase + * %% + * OBKV HBase Client Framework is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + * #L% + */ + +package com.alipay.oceanbase.hbase; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.apache.hadoop.hbase.client.*; +import com.alipay.oceanbase.rpc.exception.ObTableException; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.ClusterConnection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.util.Bytes; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static com.alipay.oceanbase.hbase.constants.OHConstants.*; + +class SqlAuditResult { + public String svrIp; + public int svrPort; + public int tabletId; + + public SqlAuditResult(String svrIp, int svrPort, int tabletId) { + this.svrIp = svrIp; + this.svrPort = svrPort; + this.tabletId = tabletId; + } + + @Override + public String toString() { + return "SqlAuditResult{" + "svrIp='" + svrIp + '\'' + ", svrPort=" + svrPort + + ", tabletId=" + tabletId + '}'; + } +} + +class ReplicaLocation { + public String zone; + public String region; + public String idc; + public String svrIp; + public int svrPort; + public String role; + + public ReplicaLocation(String zone, String region, String idc, String svrIp, int svrPort, + String role) { + this.zone = zone; + this.region = region; + this.idc = idc; + this.svrIp = svrIp; + this.svrPort = svrPort; + this.role = role; + } + + public boolean isLeader() { + return role.equalsIgnoreCase("LEADER"); + } + + public boolean isFollower() { + return role.equalsIgnoreCase("FOLLOWER"); + } + + public String getZone() { + return zone; + } + + public String getRegion() { + return region; + } + + public String getIdc() { + return idc; + } + + public String getSvrIp() { + return svrIp; + } + + public int getSvrPort() { + return svrPort; + } + + public String getRole() { + return role; + } + + public String toString() { + return "ReplicaLocation{" + "zone=" + zone + ", region=" + region + ", idc=" + idc + + ", svrIp=" + svrIp + ", svrPort=" + svrPort + ", role=" + role + '}'; + } +} + +class PartitionLocation { + ReplicaLocation leader; + List replicas; + + public PartitionLocation(ReplicaLocation leader, List replicas) { + this.leader = leader; + this.replicas = replicas; + } + + public ReplicaLocation getLeader() { + return leader; + } + + public List getReplicas() { + return replicas; + } + + public ReplicaLocation getReplicaBySvrAddr(String svrIp, int svrPort) throws Exception { + if (leader.svrIp.equals(svrIp) && leader.svrPort == svrPort) { + return leader; + } + for (ReplicaLocation replica : replicas) { + if (replica.svrIp.equals(svrIp) && replica.svrPort == svrPort) { + return replica; + } + } + throw new Exception("Failed to get replica from partition location for svrIp: " + svrIp + + " and svrPort: " + svrPort); + } + + public String toString() { + return "PartitionLocation{" + "leader=" + leader + ", replicas=" + replicas + '}'; + } +} + +/* + * CREATE TABLE IF NOT EXISTS `test_weak_read` ( + * `c1` varchar(20) NOT NULL, + * `c2` varchar(20) default NULL, + * PRIMARY KEY (`c1`) + * ) PARTITION BY KEY(`c1`) PARTITIONS 97; + */ +public class ObTableWeakReadTest { + // 测试配置常量 + private static String FULL_USER_NAME = ""; + private static String PARAM_URL = ""; + private static String PASSWORD = ""; + private static String PROXY_SYS_USER_NAME = "root"; + private static String PROXY_SYS_USER_PASSWORD = ""; + private static boolean USE_ODP = false; + private static String ODP_IP = "ip-addr"; + private static int ODP_PORT = 0; + private static String ODP_DATABASE = "database-name"; + private static String JDBC_IP = "6.12.233.118"; + private static String JDBC_PORT = "10207"; + private static String JDBC_DATABASE = "test"; + private static String JDBC_URL = "jdbc:mysql://" + + JDBC_IP + + ":" + + JDBC_PORT + + "/" + + JDBC_DATABASE + + "?rewriteBatchedStatements=TRUE&allowMultiQueries=TRUE&useLocalSessionState=TRUE&useUnicode=TRUE&characterEncoding=utf-8&socketTimeout=30000000&connectTimeout=600000&sessionVariables=ob_query_timeout=60000000000"; + + private static boolean printDebug = true; + private static int SQL_AUDIT_PERSENT = 20; + private static String TENANT_NAME = "mysql"; + private static String TABLE_NAME = "test_weak_read"; + private static String FAMILY_NAME = "cf"; + private int tenant_id = 0; + private static String FOLLOW_FIRST_ROUTE_POLICY = "FOLLOWER_FIRST"; + private static String FOLLOW_ONLY_ROUTE_POLICY = "FOLLOWER_ONLY"; + private static String ZONE1 = "zone1"; + private static String ZONE2 = "zone2"; + private static String ZONE3 = "zone3"; + private static String IDC1 = "idc1"; + private static String IDC2 = "idc2"; + private static String IDC3 = "idc3"; + private static String SWITCH_DISTRICT_SQL = "ALTER SYSTEM SET _obkv_enable_distributed_execution = ?;"; + private static String CREATE_TABLE_GROUP_SQL = "CREATE TABLEGROUP IF NOT EXISTS `%s` SHARDING = 'ADAPTIVE';"; + private static String CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS `%s$%s` ( " + + " K varbinary(1024), " + + " Q varbinary(256), " + + " T bigint, " + + " V varbinary(1048576), " + + " PRIMARY KEY(K, Q, T) " + + " ) TABLEGROUP = `%s` PARTITION BY KEY(`K`) PARTITIONS 97;"; + private static String SQL_AUDIT_SQL = "select svr_ip, svr_port, query_sql from oceanbase.GV$OB_SQL_AUDIT " + + "where query_sql like ? and query_sql like ? and tenant_id = ? limit 1;"; + private static String PARTITION_LOCATION_SQL = "SELECT t.zone, t.svr_ip, t.svr_port, t.role, z.idc, z.region " + + "FROM oceanbase.CDB_OB_TABLE_LOCATIONS t JOIN oceanbase.DBA_OB_ZONES z ON t.zone = z.zone " + + "WHERE t.table_name = ? AND t.tenant_id = ? AND t.tablet_id = ?;"; + private static String SET_SQL_AUDIT_PERSENT_SQL = "SET GLOBAL ob_sql_audit_percentage =?;"; + private static String GET_TENANT_ID_SQL = "SELECT tenant_id FROM oceanbase.__all_tenant WHERE tenant_name = ?"; + private Connection tenantConnection = null; + private Connection sysConnection = null; + private org.apache.hadoop.hbase.client.Connection connection = null; + private static Connection staticTenantConnection = null; + private static Connection staticSysConnection = null; + private static org.apache.hadoop.hbase.client.Connection staticonnection = null; + private static int staticTenantId = 0; + private static boolean clear = false; + + private static void initObkvConfig(Configuration config, String idc, String routePolicy) { + config.set(ClusterConnection.HBASE_CLIENT_CONNECTION_IMPL, + "com.alipay.oceanbase.hbase.util.OHConnectionImpl"); + if (USE_ODP) { + config.setBoolean(HBASE_OCEANBASE_ODP_MODE, true); + config.set(HBASE_OCEANBASE_ODP_ADDR, ODP_IP); + config.setInt(HBASE_OCEANBASE_ODP_PORT, ODP_PORT); + config.set(HBASE_OCEANBASE_DATABASE, ODP_DATABASE); + } else { + config.set(HBASE_OCEANBASE_PARAM_URL, PARAM_URL); + config.set(HBASE_OCEANBASE_SYS_USER_NAME, PROXY_SYS_USER_NAME); + config.set(HBASE_OCEANBASE_SYS_PASSWORD, PROXY_SYS_USER_PASSWORD); + config.set(HBASE_OCEANBASE_FULL_USER_NAME, FULL_USER_NAME); + config.set(HBASE_OCEANBASE_PASSWORD, PASSWORD); + } + if (idc != null) { + config.set(HBASE_HTABLE_CLIENT_IDC, idc); + } + config.set(HBASE_HTABLE_CLIENT_ROUTE_POLICY, routePolicy); + } + + /** + * 获取租户连接 + */ + private static Connection getConnection() throws SQLException { + String[] userNames = FULL_USER_NAME.split("#"); + return DriverManager.getConnection(JDBC_URL, userNames[0], PASSWORD); + } + + /** + * 获取系统连接 + */ + private static Connection getSysConnection() throws SQLException { + return DriverManager.getConnection(JDBC_URL, "root@sys", PASSWORD); + } + + @org.junit.BeforeClass + public static void beforeClass() throws Exception { + // 所有测试用例执行前创建表和连接(只执行一次) + staticTenantConnection = getConnection(); + staticSysConnection = getSysConnection(); + staticTenantId = getTenantId(staticSysConnection); + createTable(staticTenantConnection); + setSqlAuditPersent(staticTenantConnection, SQL_AUDIT_PERSENT); + } + + @org.junit.AfterClass + public static void afterClass() throws Exception { + if (clear) { + dropTable(staticTenantConnection); + } + } + + @Before + public void setup() throws Exception { + tenantConnection = staticTenantConnection; + sysConnection = staticSysConnection; + tenant_id = staticTenantId; + connection = staticonnection; + } + + @After + public void tearDown() throws Exception { + if (clear) { + cleanupAllData(tenantConnection); + } + } + + /* + * 切换分布式执行开关 + * + * @param connection 数据库连接 + * + * @param enable 是否开启分布式执行 + */ + private static void switchDistributedExecution(Connection connection, boolean enable) + throws Exception { + PreparedStatement statement = connection.prepareStatement(SWITCH_DISTRICT_SQL); + statement.setBoolean(1, enable); + statement.execute(); + statement.close(); + } + + /** + * 使用SQL清理所有测试数据 + * + * @param connection 数据库连接 + */ + private static void cleanupAllData(Connection connection) throws Exception { + try { + PreparedStatement statement = connection.prepareStatement("DELETE FROM " + TABLE_NAME + + "$" + FAMILY_NAME); + int deletedRows = statement.executeUpdate(); + if (printDebug) { + System.out.println("[DEBUG] Cleaned up " + deletedRows + " rows from table " + + TABLE_NAME + "$" + FAMILY_NAME); + } + statement.close(); + } catch (Exception e) { + if (printDebug) { + System.out.println("[DEBUG] Failed to cleanup data from table " + TABLE_NAME + "$" + + FAMILY_NAME + ", error: " + e.getMessage()); + } + // 清理失败不影响测试,只打印警告 + } + } + + private static void dropTable(Connection connection) throws Exception { + String sql = String.format("DROP TABLE IF EXISTS `%s$%s`", TABLE_NAME, FAMILY_NAME); + PreparedStatement statement = connection.prepareStatement(sql); + statement.execute(); + statement.close(); + dropTableGroup(connection); + } + + private static void dropTableGroup(Connection connection) throws Exception { + PreparedStatement statement = connection.prepareStatement("DROP TABLEGROUP IF EXISTS " + + TABLE_NAME); + statement.execute(); + statement.close(); + } + + private static void createTableGroup(Connection connection) throws Exception { + String sql = String.format(CREATE_TABLE_GROUP_SQL, TABLE_NAME); + PreparedStatement statement = connection.prepareStatement(sql); + statement.execute(); + statement.close(); + } + + private static void createTable(Connection connection) throws Exception { + createTableGroup(connection); + String sql = String.format(CREATE_TABLE_SQL, TABLE_NAME, FAMILY_NAME, TABLE_NAME); + PreparedStatement statement = connection.prepareStatement(sql); + statement.execute(); + statement.close(); + } + + private void setZoneIdc(String zone, String idc) throws Exception { + PreparedStatement statement = sysConnection + .prepareStatement("ALTER SYSTEM MODIFY ZONE ? SET IDC = ?;"); + statement.setString(1, zone); + statement.setString(2, idc); + debugPrint("setZoneIdc SQL: %s", statement.toString()); + statement.execute(); + } + + // 通过当前纳秒时间戳生成随机字符串 + private String getRandomRowkString() { + return System.nanoTime() + ""; + } + + // 从 querySql 中提取 tablet_id + private int extractTabletId(String querySql) { + // 查找 tablet_id:{id: 的模式 + String pattern = "tablet_id:{id:"; + int startIndex = querySql.indexOf(pattern); + if (startIndex == -1) { + return -1; + } + // 找到 id: 后面的数字开始位置 + int idStartIndex = startIndex + pattern.length(); + // 跳过可能的空格 + while (idStartIndex < querySql.length() + && Character.isWhitespace(querySql.charAt(idStartIndex))) { + idStartIndex++; + } + // 找到数字结束位置(遇到 } 或 , 或空格) + int idEndIndex = idStartIndex; + while (idEndIndex < querySql.length()) { + char c = querySql.charAt(idEndIndex); + if (c == '}' || c == ',' || c == ' ') { + break; + } + idEndIndex++; + } + // 提取数字字符串 + String tabletIdStr = querySql.substring(idStartIndex, idEndIndex).trim(); + try { + return Integer.parseInt(tabletIdStr); + } catch (NumberFormatException e) { + debugPrint("Failed to parse tablet_id from: %s", tabletIdStr); + return -1; + } + } + + private static int getTenantId(Connection connection) throws Exception { + PreparedStatement statement = connection.prepareStatement(GET_TENANT_ID_SQL); + statement.setString(1, TENANT_NAME); + ResultSet resultSet = statement.executeQuery(); + while (resultSet.next()) { + return resultSet.getInt("tenant_id"); + } + throw new ObTableException("Failed to get tenant id for tenant: " + TENANT_NAME); + } + + private static void setSqlAuditPersent(Connection connection, int persent) throws Exception { + PreparedStatement statement = connection.prepareStatement(SET_SQL_AUDIT_PERSENT_SQL); + statement.setInt(1, persent); + statement.execute(); + } + + // 通过SQL审计获取服务器地址,确认rowkey落在哪个服务器上 + private SqlAuditResult getServerBySqlAudit(String rowkey, String QuerySqlLikeString) + throws Exception { + SqlAuditResult sqlAuditResult = null; + PreparedStatement statement = tenantConnection.prepareStatement(SQL_AUDIT_SQL); + statement.setString(1, "%" + rowkey + "%"); + statement.setString(2, "%" + QuerySqlLikeString + "%"); + statement.setInt(3, this.tenant_id); + debugPrint("SQL: %s", statement.toString()); + try { + ResultSet resultSet = statement.executeQuery(); + while (resultSet.next()) { + String svrIp = resultSet.getString("svr_ip"); + int svrPort = resultSet.getInt("svr_port"); + String querySql = resultSet.getString("query_sql"); + int tabletId = extractTabletId(querySql); + sqlAuditResult = new SqlAuditResult(svrIp, svrPort, tabletId); + debugPrint("querySql: %s", querySql); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + } + resultSet.close(); + } finally { + statement.close(); + } + + if (sqlAuditResult == null) { + throw new ObTableException("Failed to get server address from sql audit for rowkey: " + + rowkey + " and QuerySqlLikeString: " + QuerySqlLikeString); + } + + return sqlAuditResult; + } + + private PartitionLocation getPartitionLocation(int tabletId) throws Exception { + ReplicaLocation leader = null; + List replicas = new ArrayList<>(); + PreparedStatement statement = sysConnection.prepareStatement(PARTITION_LOCATION_SQL); + statement.setString(1, TABLE_NAME + "$" + FAMILY_NAME); + statement.setInt(2, this.tenant_id); // 使用成员变量 tenant_id + statement.setInt(3, tabletId); + debugPrint("PARTITION_LOCATION_SQL: %s", statement.toString()); + ResultSet resultSet = statement.executeQuery(); + while (resultSet.next()) { + String zone = resultSet.getString("zone"); + String region = resultSet.getString("region"); + String idc = resultSet.getString("idc"); + String svrIp = resultSet.getString("svr_ip"); + int svrPort = resultSet.getInt("svr_port"); + String role = resultSet.getString("role"); + if (role.equalsIgnoreCase("LEADER")) { + leader = new ReplicaLocation(zone, region, idc, svrIp, svrPort, role); + } else { + replicas.add(new ReplicaLocation(zone, region, idc, svrIp, svrPort, role)); + } + } + if (leader == null) { + throw new ObTableException("Failed to get leader from partition location for tabletId: " + tabletId); + } + return new PartitionLocation(leader, replicas); + } + + private void insertData(Table table, String rowkey) throws Exception { + Put put = new Put(rowkey.getBytes()); + put.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes(), "c2_val".getBytes()); + table.put(put); + } + + /** + * 封装debug打印方法 + * + * @param message 要打印的消息 + */ + private void debugPrint(String message) { + if (printDebug) { + System.out.println("[DEBUG] " + message); + } + } + + /** + * 封装debug打印方法(支持格式化) + * + * @param format 格式化字符串 + * @param args 参数 + */ + private void debugPrint(String format, Object... args) { + if (printDebug) { + System.out.println("[DEBUG] " + String.format(format, args)); + } + } + + /* + * 测试场景:用户正常使用场景,使用get接口进行指定IDC读 + * 测试预期:发到对应的IDC上进行读取 + */ + @Test + public void testIdcGet1() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:未设置当前IDC进行弱读 + * 测试预期:发到任意follower上进行弱读 + */ + @Test + public void testIdcGet2() throws Exception { + Configuration config = HBaseConfiguration.create(); + // 不设置 IDC + initObkvConfig(config, null, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:指定了IDC,但是没有指定弱读 + * 测试预期:发到leader副本上进行读取 + */ + @Test + public void testIdcGet3() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据,不设置弱读 + Get get = new Get(rowkey.getBytes()); + // 不设置 weak consistency + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置不存在的IDC进行弱读 + * 测试预期:fallback到其他可用的副本(sameRegion或otherRegion) + */ + @Test + public void testIdcGet4() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, "invalid_idc", FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower,且IDC不是invalid_idc + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertNotEquals("invalid_idc", readReplica.getIdc()); + } + + /* + * 测试场景:设置IDC并使用strong consistency + * 测试预期:即使设置了IDC,strong consistency也应该读leader + */ + @Test + public void testIdcGet5() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据,使用strong consistency + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:strong consistency应该读leader,即使设置了IDC + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置空字符串IDC进行弱读 + * 测试预期:fallback到其他可用的副本 + */ + @Test + public void testIdcGet6() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, "", FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:使用非法的ReadConsistency值 + * 测试预期:可能不会抛出异常,但应该被忽略或使用默认值 + * 注意:HBase API 中,非法的 consistency 值可能不会立即抛出异常,而是在服务端处理 + */ + @Test + public void testIdcGet7() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 使用非法的ReadConsistency值 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "invalid_consistency".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + // 执行查询,可能不会抛出异常,但应该使用默认的 strong consistency + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:非法的 consistency 值应该被忽略,使用默认的 strong,读 leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + // 非法的值应该被忽略,使用默认的 strong consistency + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置IDC但该IDC没有该分区的副本(极端情况) + * 测试预期:fallback到其他region的副本 + */ + @Test + public void testIdcGet8() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, "idc4", FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc(只设置IDC1, IDC2, IDC3,不设置IDC4) + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower,且IDC不是idc4 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertNotEquals("idc4", readReplica.getIdc()); + } + + /* + * 测试场景:使用null作为ReadConsistency(使用默认值) + * 测试预期:使用默认的strong consistency,读leader + */ + @Test + public void testIdcGet9() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 不设置ReadConsistency,使用默认值(应该是strong) + Get get = new Get(rowkey.getBytes()); + // 不设置 weak consistency,使用默认值 + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:默认应该是strong,读leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置IDC,但使用大小写不同的weak值 + * 测试预期:应该能正常识别(不区分大小写) + */ + @Test + public void testIdcGet10() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 使用不同大小写的weak + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "WEAK".getBytes()); // 大写 + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:关闭分布式开关,用户正常使用场景,使用get接口进行指定IDC读 + * 测试预期:发到对应的IDC上进行读取 + */ + @Test + public void testIdcGet11() throws Exception { + try { + // 1. 关闭分布式开关 + switchDistributedExecution(tenantConnection, false); + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr( + sqlAuditResult.svrIp, sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } finally { + // 8. 恢复分布式开关 + switchDistributedExecution(tenantConnection, true); + } + } + + /* + * 测试场景:不改代码的用户,在配置项中设置全局弱一致性读 + * 测试预期:发到对应的IDC上进行读取 + */ + @Test + public void testIdcGet12() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "weak"); // 设置全局弱一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Get get = new Get(rowkey.getBytes()); + // get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); // + // 不设置弱一致性读 + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + * 测试预期:在存在follower场景下,能够路由到follower,且能够读到数据,但读follower副本 + */ + @Test + public void testRoutePolicyGet1() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null/* IDC */, FOLLOW_ONLY_ROUTE_POLICY); // 不设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + 设置IDC + weak read + * 测试预期:路由到指定IDC的follower副本 + */ + @Test + public void testRoutePolicyGet2() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_ONLY_ROUTE_POLICY); // 设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + strong read + * 测试预期:即使使用FOLLOW_ONLY_ROUTE_POLICY,strong read也应该读leader + */ + @Test + public void testRoutePolicyGet3() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null/* IDC */, FOLLOW_ONLY_ROUTE_POLICY); // 不设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据,使用strong consistency + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验:strong read应该读leader,即使使用FOLLOW_ONLY_ROUTE_POLICY + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + 设置IDC + strong read + * 测试预期:即使使用FOLLOW_ONLY_ROUTE_POLICY和设置IDC,strong read也应该读leader + */ + @Test + public void testRoutePolicyGet4() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_ONLY_ROUTE_POLICY); // 设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据,使用strong consistency + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验:strong read应该读leader,即使使用FOLLOW_ONLY_ROUTE_POLICY和设置IDC + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:路由策略对比测试,使用FOLLOW_FIRST_ROUTE_POLICY + 不设置IDC + weak read + * 测试预期:能够路由到follower,与FOLLOW_ONLY_ROUTE_POLICY行为类似 + */ + @Test + public void testRoutePolicyGet5() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null/* IDC */, FOLLOW_FIRST_ROUTE_POLICY); // 不设置IDC,使用FOLLOW_FIRST_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验:应该读到follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:用户正常使用场景,使用scan接口进行指定IDC读 + * 测试预期:发到对应的IDC上进行读取 + */ + @Test + public void testIdcScan1() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:未设置当前IDC进行弱读 + * 测试预期:发到任意follower上进行弱读 + */ + @Test + public void testIdcScan2() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:指定了IDC,但是没有指定弱读 + * 测试预期:发到leader副本上进行读取 + */ + @Test + public void testIdcScan3() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据,不设置弱读 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + // 不设置 weak consistency + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置不存在的IDC进行弱读 + * 测试预期:fallback到其他可用的副本(sameRegion或otherRegion) + */ + @Test + public void testIdcScan4() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, "invalid_idc", FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower,且IDC不是invalid_idc + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertNotEquals("invalid_idc", readReplica.getIdc()); + } + + /* + * 测试场景:设置IDC并使用strong consistency + * 测试预期:即使设置了IDC,strong consistency也应该读leader + */ + @Test + public void testIdcScan5() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据,使用strong consistency + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:strong consistency应该读leader,即使设置了IDC + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置空字符串IDC进行弱读 + * 测试预期:fallback到其他可用的副本 + */ + @Test + public void testIdcScan6() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, "", FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:使用非法的ReadConsistency值 + * 测试预期:可能不会抛出异常,但应该被忽略或使用默认值 + * 注意:HBase API 中,非法的 consistency 值可能不会立即抛出异常,而是在服务端处理 + */ + @Test + public void testIdcScan7() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 使用非法的ReadConsistency值 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "invalid_consistency".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + // 执行查询,可能不会抛出异常,但应该使用默认的 strong consistency + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:非法的 consistency 值应该被忽略,使用默认的 strong,读 leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + // 非法的值应该被忽略,使用默认的 strong consistency + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置IDC但该IDC没有该分区的副本(极端情况) + * 测试预期:fallback到其他region的副本 + */ + @Test + public void testIdcScan8() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, "idc4", FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc(只设置IDC1, IDC2, IDC3,不设置IDC4) + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower,且IDC不是idc4 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertNotEquals("idc4", readReplica.getIdc()); + } + + /* + * 测试场景:使用null作为ReadConsistency(使用默认值) + * 测试预期:使用默认的strong consistency,读leader + */ + @Test + public void testIdcScan9() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 不设置ReadConsistency,使用默认值(应该是strong) + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + // 不设置 weak consistency,使用默认值 + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:默认应该是strong,读leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置IDC,但使用大小写不同的weak值 + * 测试预期:应该能正常识别(不区分大小写) + */ + @Test + public void testIdcScan10() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 使用不同大小写的weak + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "WEAK".getBytes()); // 大写 + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:关闭分布式开关,用户正常使用场景,使用scan接口进行指定IDC读 + * 测试预期:发到对应的IDC上进行读取 + */ + @Test + public void testIdcScan11() throws Exception { + try { + // 1. 关闭分布式开关 + switchDistributedExecution(tenantConnection, false); + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr( + sqlAuditResult.svrIp, sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } finally { + // 8. 恢复分布式开关 + switchDistributedExecution(tenantConnection, true); + } + } + + /* + * 测试场景:不改代码的用户,在配置项中设置全局弱一致性读 + * 测试预期:发到对应的IDC上进行读取 + */ + @Test + public void testIdcScan12() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "weak"); // 设置全局弱一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + // scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); // + // 不设置弱一致性读 + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + * 测试预期:在存在follower场景下,能够路由到follower,且能够读到数据,但读follower副本 + */ + @Test + public void testRoutePolicyScan1() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null/* IDC */, FOLLOW_ONLY_ROUTE_POLICY); // 不设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + 设置IDC + weak read + * 测试预期:路由到指定IDC的follower副本 + */ + @Test + public void testRoutePolicyScan2() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_ONLY_ROUTE_POLICY); // 设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + strong read + * 测试预期:即使使用FOLLOW_ONLY_ROUTE_POLICY,strong read也应该读leader + */ + @Test + public void testRoutePolicyScan3() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null/* IDC */, FOLLOW_ONLY_ROUTE_POLICY); // 不设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据,使用strong consistency + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验:strong read应该读leader,即使使用FOLLOW_ONLY_ROUTE_POLICY + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + 设置IDC + strong read + * 测试预期:即使使用FOLLOW_ONLY_ROUTE_POLICY和设置IDC,strong read也应该读leader + */ + @Test + public void testRoutePolicyScan4() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_ONLY_ROUTE_POLICY); // 设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据,使用strong consistency + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验:strong read应该读leader,即使使用FOLLOW_ONLY_ROUTE_POLICY和设置IDC + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:路由策略对比测试,使用FOLLOW_FIRST_ROUTE_POLICY + 不设置IDC + weak read + * 测试预期:能够路由到follower,与FOLLOW_ONLY_ROUTE_POLICY行为类似 + */ + @Test + public void testRoutePolicyScan5() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null/* IDC */, FOLLOW_FIRST_ROUTE_POLICY); // 不设置IDC,使用FOLLOW_FIRST_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory + .createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + scan.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验:应该读到follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:用户正常使用场景,使用batch get接口进行指定IDC读 + * 测试预期:发到对应的IDC上进行读取 + */ + @Test + public void testIdcBatchGet1() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(2, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:未设置当前IDC进行弱读 + * 测试预期:发到任意follower上进行弱读 + */ + @Test + public void testIdcBatchGet2() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:指定了IDC,但是没有指定弱读 + * 测试预期:发到leader副本上进行读取 + */ + @Test + public void testIdcBatchGet3() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据,不设置弱读 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + // 不设置 weak consistency + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置不存在的IDC进行弱读 + * 测试预期:fallback到其他可用的副本(sameRegion或otherRegion) + */ + @Test + public void testIdcBatchGet4() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, "invalid_idc", FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower,且IDC不是invalid_idc + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertNotEquals("invalid_idc", readReplica.getIdc()); + } + + /* + * 测试场景:设置IDC并使用strong consistency + * 测试预期:即使设置了IDC,strong consistency也应该读leader + */ + @Test + public void testIdcBatchGet5() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据,使用strong consistency + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:strong consistency应该读leader,即使设置了IDC + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置空字符串IDC进行弱读 + * 测试预期:fallback到其他可用的副本 + */ + @Test + public void testIdcBatchGet6() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, "", FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:使用非法的ReadConsistency值 + * 测试预期:可能不会抛出异常,但应该被忽略或使用默认值 + * 注意:HBase API 中,非法的 consistency 值可能不会立即抛出异常,而是在服务端处理 + */ + @Test + public void testIdcBatchGet7() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 使用非法的ReadConsistency值 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "invalid_consistency".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + // 执行查询,可能不会抛出异常,但应该使用默认的 strong consistency + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:非法的 consistency 值应该被忽略,使用默认的 strong,读 leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + // 非法的值应该被忽略,使用默认的 strong consistency + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置IDC但该IDC没有该分区的副本(极端情况) + * 测试预期:fallback到其他region的副本 + */ + @Test + public void testIdcBatchGet8() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, "idc4", FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc(只设置IDC1, IDC2, IDC3,不设置IDC4) + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower,且IDC不是idc4 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertNotEquals("idc4", readReplica.getIdc()); + } + + /* + * 测试场景:使用null作为ReadConsistency(使用默认值) + * 测试预期:使用默认的strong consistency,读leader + */ + @Test + public void testIdcBatchGet9() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 不设置ReadConsistency,使用默认值(应该是strong) + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + // 不设置 weak consistency,使用默认值 + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:默认应该是strong,读leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:设置IDC,但使用大小写不同的weak值 + * 测试预期:应该能正常识别(不区分大小写) + */ + @Test + public void testIdcBatchGet10() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 使用不同大小写的weak + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "WEAK".getBytes()); // 大写 + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该读到follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:关闭分布式开关,用户正常使用场景,使用batch get接口进行指定IDC读 + * 测试预期:发到对应的IDC上进行读取 + */ + @Test + public void testIdcBatchGet11() throws Exception { + try { + // 1. 关闭分布式开关 + switchDistributedExecution(tenantConnection, false); + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(2, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } finally { + // 8. 恢复分布式开关 + switchDistributedExecution(tenantConnection, true); + } + } + + /* + * 测试场景:不改代码的用户,在配置项中设置全局弱一致性读 + * 测试预期:发到对应的IDC上进行读取 + */ + @Test + public void testIdcBatchGet12() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "weak"); // 设置全局弱一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + // get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); // + // 不设置弱一致性读 + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + + // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + * 测试预期:在存在follower场景下,能够路由到follower,且能够读到数据,但读follower副本 + */ + @Test + public void testRoutePolicyBatchGet1() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null/* IDC */, FOLLOW_ONLY_ROUTE_POLICY); // 不设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + 设置IDC + weak read + * 测试预期:路由到指定IDC的follower副本 + */ + @Test + public void testRoutePolicyBatchGet2() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_ONLY_ROUTE_POLICY); // 设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验 + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + strong read + * 测试预期:即使使用FOLLOW_ONLY_ROUTE_POLICY,strong read也应该读leader + */ + @Test + public void testRoutePolicyBatchGet3() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null/* IDC */, FOLLOW_ONLY_ROUTE_POLICY); // 不设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据,使用strong consistency + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验:strong read应该读leader,即使使用FOLLOW_ONLY_ROUTE_POLICY + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:路由策略测试,使用FOLLOW_ONLY_ROUTE_POLICY + 设置IDC + strong read + * 测试预期:即使使用FOLLOW_ONLY_ROUTE_POLICY和设置IDC,strong read也应该读leader + */ + @Test + public void testRoutePolicyBatchGet4() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_ONLY_ROUTE_POLICY); // 设置IDC,使用FOLLOW_ONLY_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据,使用strong consistency + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验:strong read应该读leader,即使使用FOLLOW_ONLY_ROUTE_POLICY和设置IDC + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:路由策略对比测试,使用FOLLOW_FIRST_ROUTE_POLICY + 不设置IDC + weak read + * 测试预期:能够路由到follower,与FOLLOW_ONLY_ROUTE_POLICY行为类似 + */ + @Test + public void testRoutePolicyBatchGet5() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, null/* IDC */, FOLLOW_FIRST_ROUTE_POLICY); // 不设置IDC,使用FOLLOW_FIRST_ROUTE_POLICY + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 2. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); // 等待数据同步到所有节点 + // 3. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 4. 获取数据 + List gets = new ArrayList<>(); + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(1, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 5. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 6. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 7. 校验:应该读到follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + } +} From 0d01752eb3a99d361a2a24fd23190c3d41d60089 Mon Sep 17 00:00:00 2001 From: JackShi148 Date: Tue, 18 Nov 2025 15:00:21 +0800 Subject: [PATCH 08/15] review advice --- .../com/alipay/oceanbase/hbase/OHTable.java | 74 +++++++++---------- .../hbase/result/ClientStreamScanner.java | 4 +- .../oceanbase/hbase/util/MetricsImporter.java | 12 +-- .../hbase/util/OHMetricsTracker.java | 2 +- 4 files changed, 43 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index f8f29a4b..eb61f217 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -204,8 +204,7 @@ public OHTable(Configuration configuration, String tableName) throws IOException setOperationTimeout(ohConnectionConf.getClientOperationTimeout()); if (configuration.getBoolean(CLIENT_SIDE_METRICS_ENABLED_KEY, false)) { this.metrics = new OHMetrics(OHBaseFuncUtils.metricsNameBuilder(tableNameString, - obTableClient.getDatabase(), - obTableClient.getClusterName())); + obTableClient.getDatabase())); } else { this.metrics = null; } @@ -263,8 +262,7 @@ public OHTable(Configuration configuration, final byte[] tableName, setOperationTimeout(ohConnectionConf.getClientOperationTimeout()); if (configuration.getBoolean(CLIENT_SIDE_METRICS_ENABLED_KEY, false)) { this.metrics = new OHMetrics(OHBaseFuncUtils.metricsNameBuilder(tableNameString, - obTableClient.getDatabase(), - obTableClient.getClusterName())); + obTableClient.getDatabase())); } else { this.metrics = null; } @@ -339,8 +337,7 @@ public OHTable(TableName tableName, Connection connection, setOperationTimeout(operationTimeout); if (configuration.getBoolean(CLIENT_SIDE_METRICS_ENABLED_KEY, false)) { this.metrics = new OHMetrics(OHBaseFuncUtils.metricsNameBuilder(tableNameString, - obTableClient.getDatabase(), - obTableClient.getClusterName())); + obTableClient.getDatabase())); } else { this.metrics = null; } @@ -386,8 +383,7 @@ public OHTable(Connection connection, ObTableBuilderBase builder, setOperationTimeout(operationTimeout); if (configuration.getBoolean(CLIENT_SIDE_METRICS_ENABLED_KEY, false)) { this.metrics = new OHMetrics(OHBaseFuncUtils.metricsNameBuilder(tableNameString, - obTableClient.getDatabase(), - obTableClient.getClusterName())); + obTableClient.getDatabase())); } else { this.metrics = null; } @@ -485,10 +481,10 @@ public static OHConnectionConfiguration setUserDefinedNamespace(String tableName private abstract class OperationExecuteCallback { private final OHOperationType opType; - private final long singleOpCount; - OperationExecuteCallback(OHOperationType opType, long singleOpCount) { + private final long batchSize; + OperationExecuteCallback(OHOperationType opType, long batchSize) { this.opType = opType; - this.singleOpCount = singleOpCount; + this.batchSize = batchSize; } abstract T execute() throws IOException; @@ -496,8 +492,8 @@ public OHOperationType getOpType() { return this.opType; } - public long getSingleOpCount() { - return this.singleOpCount; + public long getBatchSize() { + return this.batchSize; } } @@ -505,7 +501,7 @@ private T execute(OperationExecuteCallback callback) throws IOException { if (this.metrics != null) { long startTimeMs = System.currentTimeMillis(); MetricsImporter importer = new MetricsImporter(); - importer.setSingleOpCount(callback.getSingleOpCount()); + importer.setBatchSize(callback.getBatchSize()); try { return callback.execute(); } catch (Exception e) { @@ -560,7 +556,7 @@ public TableDescriptor getDescriptor() throws IOException { @Override public boolean exists(Get get) throws IOException { OHOperationType opType = OHOperationType.EXISTS; - return execute(new OperationExecuteCallback(opType, 1) { + return execute(new OperationExecuteCallback(opType, 1 /* batchSize */) { @Override Boolean execute() throws IOException { Get newGet = new Get(get); @@ -573,17 +569,16 @@ Boolean execute() throws IOException { @Override public boolean[] existsAll(List gets) throws IOException { OHOperationType opType = OHOperationType.EXISTS_LIST; - return execute(new OperationExecuteCallback(opType, gets.size()) { + return execute(new OperationExecuteCallback(opType, gets.size() /* batchSize */) { @Override boolean[] execute() throws IOException { boolean[] ret = new boolean[gets.size()]; List newGets = new ArrayList<>(); // if just checkExistOnly, batch get will not return any result or row count // therefore we have to set checkExistOnly as false and so the result can be returned - // TODO: adjust ExistOnly in server when using batch get for (Get get : gets) { Get newGet = new Get(get); - newGet.setCheckExistenceOnly(false); + newGet.setCheckExistenceOnly(true); newGets.add(newGet); } Result[] results = new Result[newGets.size()]; @@ -591,7 +586,7 @@ boolean[] execute() throws IOException { innerBatchImpl(newGets, results, opType); } else { for (int i = 0; i < newGets.size(); i++) { - results[i] = innerGetImpl(newGets.get(i), opType); // TODO:循环执行的类型用什么? + results[i] = innerGetImpl(newGets.get(i), opType); // still use list type even executing gets one by one in loop } } for (int i = 0; i < results.length; ++i) { @@ -757,11 +752,11 @@ private void compatOldServerBatch(final List actions, final Objec @Override public void batch(final List actions, final Object[] results) throws IOException { OHOperationType opType = OHOperationType.BATCH; - execute(new OperationExecuteCallback(opType, actions.size()) { + execute(new OperationExecuteCallback(opType, actions.size() /* batchSize */) { @Override public Void execute() throws IOException { innerBatchImpl(actions, results, opType); - return null; + return null; // return null for the return type Void, primitive type like void cannot be template type } }); } @@ -912,12 +907,12 @@ public void batchCallback(List actions, Object[] results, Batch.Callback callback) throws IOException, InterruptedException { OHOperationType opType = OHOperationType.BATCH_CALLBACK; - execute(new OperationExecuteCallback(opType, actions.size()) { + execute(new OperationExecuteCallback(opType, actions.size() /* batchSize */) { @Override public Void execute() throws IOException { try { innerBatchImpl(actions, results, opType); - return null; + return null; // return null for the return type Void, primitive type like void cannot be template type } finally { if (results != null) { for (int i = 0; i < results.length; i++) { @@ -1015,7 +1010,7 @@ private void processColumnFilters(NavigableSet columnFilters, @Override public Result get(final Get get) throws IOException { OHOperationType opType = OHOperationType.GET; - return execute(new OperationExecuteCallback(opType, 1) { + return execute(new OperationExecuteCallback(opType, 1 /* batchSize */) { @Override Result execute() throws IOException { return innerGetImpl(get, opType); @@ -1102,7 +1097,7 @@ public Result call() throws IOException { @Override public Result[] get(List gets) throws IOException { OHOperationType opType = OHOperationType.GET_LIST; - return execute(new OperationExecuteCallback(opType, gets.size()) { + return execute(new OperationExecuteCallback(opType, gets.size() /* batchSize */) { @Override Result[] execute() throws IOException { Result[] results = new Result[gets.size()]; @@ -1110,7 +1105,7 @@ Result[] execute() throws IOException { innerBatchImpl(gets, results, opType); } else { for (int i = 0; i < gets.size(); i++) { - results[i] = innerGetImpl(gets.get(i), opType); // TODO: 这种单次循环执行的类型是用 LIST 类型还是用 EXIST 类型? + results[i] = innerGetImpl(gets.get(i), opType); // still use list type even executing gets one by one in loop } } return results; @@ -1120,7 +1115,7 @@ Result[] execute() throws IOException { @Override public ResultScanner getScanner(final Scan scan) throws IOException { - return execute(new OperationExecuteCallback(OHOperationType.SCAN, 1) { + return execute(new OperationExecuteCallback(OHOperationType.SCAN, 1 /* batchSize */) { @Override ResultScanner execute() throws IOException { if (scan.getFamilyMap().keySet().isEmpty()) { @@ -1329,11 +1324,11 @@ public ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOExcept @Override public void put(Put put) throws IOException { OHOperationType opType = OHOperationType.PUT; - execute(new OperationExecuteCallback(opType, 1) { + execute(new OperationExecuteCallback(opType, 1 /* batchSize */) { @Override public Void execute() throws IOException { doPut(Collections.singletonList(put), opType); - return null; + return null; // return null for the return type Void, primitive type like void cannot be template type } }); } @@ -1341,11 +1336,11 @@ public Void execute() throws IOException { @Override public void put(List puts) throws IOException { OHOperationType opType = OHOperationType.PUT_LIST; - execute(new OperationExecuteCallback(opType, puts.size()) { + execute(new OperationExecuteCallback(opType, puts.size() /* batchSize */) { @Override public Void execute() throws IOException { doPut(puts, opType); - return null; + return null; // return null for the return type Void, primitive type like void cannot be template type } }); } @@ -1455,12 +1450,12 @@ private void innerDelete(List deletes, OHOperationType opType) throws IO @Override public void delete(Delete delete) throws IOException { OHOperationType opType = OHOperationType.DELETE; - execute(new OperationExecuteCallback(opType, 1) { + execute(new OperationExecuteCallback(opType, 1 /* batchSize */) { @Override public Void execute() throws IOException { checkFamilyViolation(delete.getFamilyCellMap().keySet(), false); innerDelete(Collections.singletonList(delete), opType); - return null; + return null; // return null for the return type Void, primitive type like void cannot be template type } }); } @@ -1468,11 +1463,11 @@ public Void execute() throws IOException { @Override public void delete(List deletes) throws IOException { OHOperationType opType = OHOperationType.DELETE_LIST; - execute(new OperationExecuteCallback(opType, deletes.size()) { + execute(new OperationExecuteCallback(opType, deletes.size() /* batchSize */) { @Override public Void execute() throws IOException { innerDelete(deletes, opType); - return null; + return null; // return null for the return type Void, primitive type like void cannot be template type } }); } @@ -1532,7 +1527,7 @@ private boolean checkAndMutation(byte[] row, byte[] family, byte[] qualifier, CompareFilter.CompareOp compareOp, byte[] value, TimeRange timeRange, RowMutations rowMutations, OHOperationType opType) throws IOException { - return execute(new OperationExecuteCallback(opType, rowMutations.getMutations().size()) { + return execute(new OperationExecuteCallback(opType, rowMutations.getMutations().size() /* batchSize */) { @Override Boolean execute() throws IOException { try { @@ -1587,7 +1582,7 @@ public void mutateRow(RowMutations rm) { @Override public Result append(Append append) throws IOException { OHOperationType opType = OHOperationType.APPEND; - return execute(new OperationExecuteCallback(opType, 1) { + return execute(new OperationExecuteCallback(opType, 1 /* batchSize */) { @Override Result execute() throws IOException { checkArgument(!append.isEmpty(), "Invalid arguments to %s, zero columns specified", @@ -1647,7 +1642,7 @@ Result execute() throws IOException { @Override public Result increment(Increment increment) throws IOException { OHOperationType opType = OHOperationType.INCREMENT; - return execute(new OperationExecuteCallback(opType, 1) { + return execute(new OperationExecuteCallback(opType, 1 /* batchSize */) { @Override Result execute() throws IOException { checkArgument(!increment.isEmpty(), "Invalid arguments to %s, zero columns specified", increment.toString()); @@ -1707,7 +1702,7 @@ Result execute() throws IOException { public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount) throws IOException { OHOperationType opType = OHOperationType.INCREMENT_COLUMN_VALUE; - return execute(new OperationExecuteCallback(opType, 1) { + return execute(new OperationExecuteCallback(opType, 1 /* batchSize */) { @Override Long execute() throws IOException { try { @@ -2789,4 +2784,5 @@ private void checkCmpOp() { public OHMetrics getMetrics() { return metrics; } + } \ No newline at end of file diff --git a/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java b/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java index 4fe538ff..5d588d87 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java +++ b/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java @@ -33,10 +33,8 @@ import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.util.Bytes; -import org.mortbay.util.SingletonList; import org.slf4j.Logger; import java.io.IOException; -import java.nio.ByteBuffer; import java.util.*; import static com.alipay.oceanbase.hbase.util.TableHBaseLoggerFactory.LCD; @@ -148,7 +146,7 @@ public Result next() throws IOException { if (metrics != null) { long duration = System.currentTimeMillis() - startTimeMs; importer.setDuration(duration); - importer.setSingleOpCount(1); + importer.setBatchSize(1); metrics.update(new ObPair(OHOperationType.SCAN, importer)); } } diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java index e2c6ff17..d442fccf 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java @@ -3,12 +3,12 @@ public class MetricsImporter { private boolean isFailedOp; private long duration; - private long singleOpCount; + private long batchSize; public MetricsImporter() { this.isFailedOp = false; this.duration = 0; - this.singleOpCount = 0; + this.batchSize = 0; } public void setIsFailedOp(boolean isFailedOp) { @@ -19,8 +19,8 @@ public void setDuration(long duration) { this.duration = duration; } - public void setSingleOpCount(long singleOpCount) { - this.singleOpCount = singleOpCount; + public void setBatchSize(long batchSize) { + this.batchSize = batchSize; } public boolean isFailedOp() { @@ -31,7 +31,7 @@ public long getDuration() { return duration; } - public long getSingleOpCount() { - return singleOpCount; + public long getBatchSize() { + return batchSize; } } diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java index e152e29d..dda0929f 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java @@ -36,7 +36,7 @@ public void update(MetricsImporter importer) { if (importer.isFailedOp()) { failedOpCounter.mark(); } - totalSingleOpCount.inc(importer.getSingleOpCount()); + totalSingleOpCount.inc(importer.getBatchSize()); totalRuntime.inc(importer.getDuration()); } From ace9d913b7dd65b507a3a7758e22e080e1ec59dc Mon Sep 17 00:00:00 2001 From: WeiXinChan Date: Thu, 27 Nov 2025 12:01:23 +0800 Subject: [PATCH 09/15] adapt weak read new interface --- src/main/java/com/alipay/oceanbase/hbase/OHTable.java | 6 +++--- .../hbase/execute/AbstractObTableMetaExecutor.java | 1 - .../alipay/oceanbase/hbase/util/ObTableClientManager.java | 5 +++-- .../com/alipay/oceanbase/hbase/ObTableWeakReadTest.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index f6b55441..41b5e7c6 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -2382,7 +2382,7 @@ private BatchOperation buildBatchOperation(String tableName, List try { query.setRowKey(row(colVal("K", Bytes.toString(get.getRow())), colVal("Q", null), colVal("T", Integer.MAX_VALUE))); if (isWeakRead(get)) { - query.setReadConsistency("weak"); + query.setReadConsistency(ObReadConsistency.WEAK); } } catch (Exception e) { logger.error("unexpected error occurs when set row key", e); @@ -2568,7 +2568,7 @@ private ObTableQueryRequest buildObTableQueryRequest(ObTableQuery obTableQuery, request.setTableQuery(obTableQuery); request.setTableName(targetTableName); if (isWeakRead) { - request.setConsistencyLevel(ObTableConsistencyLevel.EVENTUAL); + request.setConsistencyLevel(ObReadConsistency.WEAK); } request.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); request.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); @@ -2591,7 +2591,7 @@ private ObTableQueryAsyncRequest buildObTableQueryAsyncRequest(ObTableQuery obTa asyncRequest.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); asyncRequest.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); if (isWeakRead) { - asyncRequest.setConsistencyLevel(ObTableConsistencyLevel.EVENTUAL); + asyncRequest.setConsistencyLevel(ObReadConsistency.WEAK); } asyncRequest.setHbaseOpType(opType); return asyncRequest; diff --git a/src/main/java/com/alipay/oceanbase/hbase/execute/AbstractObTableMetaExecutor.java b/src/main/java/com/alipay/oceanbase/hbase/execute/AbstractObTableMetaExecutor.java index 05b48b4a..d5d321e5 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/execute/AbstractObTableMetaExecutor.java +++ b/src/main/java/com/alipay/oceanbase/hbase/execute/AbstractObTableMetaExecutor.java @@ -19,7 +19,6 @@ import com.alipay.oceanbase.hbase.util.OHBaseExceptionUtil; import com.alipay.oceanbase.rpc.ObTableClient; -import com.alipay.oceanbase.rpc.exception.ObTableException; import com.alipay.oceanbase.rpc.meta.ObTableMetaRequest; import com.alipay.oceanbase.rpc.meta.ObTableMetaResponse; import com.alipay.oceanbase.rpc.table.ObTable; diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java b/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java index 85e10930..ac1cd357 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java @@ -20,6 +20,7 @@ import com.alipay.oceanbase.rpc.ObTableClient; import com.alipay.oceanbase.rpc.constant.Constants; import com.alipay.oceanbase.rpc.location.model.ObRoutePolicy; +import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.ObReadConsistency; import com.alipay.oceanbase.hbase.OHTable; import com.google.common.base.Objects; import org.apache.hadoop.classification.InterfaceAudience; @@ -123,10 +124,10 @@ public static ObTableClient getOrCreateObTableClient(ObTableClientKey obTableCli obTableClient.setCurrentIDC(connectionConfig.getIdc()); } if (connectionConfig.getRoutePolicy() != null) { - obTableClient.setRoutePolicy(connectionConfig.getRoutePolicy()); + obTableClient.setRoutePolicy(ObRoutePolicy.getByName(connectionConfig.getRoutePolicy())); } if (connectionConfig.getGlobalWeakRead() != null) { - obTableClient.setReadConsistency(connectionConfig.getGlobalWeakRead());; + obTableClient.setReadConsistency(ObReadConsistency.getByName(connectionConfig.getGlobalWeakRead())); } obTableClient.init(); OB_TABLE_CLIENT_INSTANCE.put(obTableClientKey, obTableClient); diff --git a/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java b/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java index a02423a6..17b1bf77 100644 --- a/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java +++ b/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java @@ -169,8 +169,8 @@ public class ObTableWeakReadTest { private static String ODP_IP = "ip-addr"; private static int ODP_PORT = 0; private static String ODP_DATABASE = "database-name"; - private static String JDBC_IP = "6.12.233.118"; - private static String JDBC_PORT = "10207"; + private static String JDBC_IP = ""; + private static String JDBC_PORT = ""; private static String JDBC_DATABASE = "test"; private static String JDBC_URL = "jdbc:mysql://" + JDBC_IP From cd110dcef3874c63d1d1b03d89b26d3852f6f336 Mon Sep 17 00:00:00 2001 From: WeiXinChan Date: Thu, 4 Dec 2025 22:10:53 +0800 Subject: [PATCH 10/15] fix weak read in batch get --- .../com/alipay/oceanbase/hbase/OHTable.java | 2 +- .../oceanbase/hbase/ObTableWeakReadTest.java | 77 ++++++++++++++++++- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index 41b5e7c6..181e456f 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -2382,7 +2382,7 @@ private BatchOperation buildBatchOperation(String tableName, List try { query.setRowKey(row(colVal("K", Bytes.toString(get.getRow())), colVal("Q", null), colVal("T", Integer.MAX_VALUE))); if (isWeakRead(get)) { - query.setReadConsistency(ObReadConsistency.WEAK); + batch.setReadConsistency(ObReadConsistency.WEAK); } } catch (Exception e) { logger.error("unexpected error occurs when set row key", e); diff --git a/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java b/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java index 17b1bf77..36a65723 100644 --- a/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java +++ b/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java @@ -168,6 +168,7 @@ public class ObTableWeakReadTest { private static boolean USE_ODP = false; private static String ODP_IP = "ip-addr"; private static int ODP_PORT = 0; + private static int ODP_SQL_PORT = 0; private static String ODP_DATABASE = "database-name"; private static String JDBC_IP = ""; private static String JDBC_PORT = ""; @@ -179,6 +180,13 @@ public class ObTableWeakReadTest { + "/" + JDBC_DATABASE + "?rewriteBatchedStatements=TRUE&allowMultiQueries=TRUE&useLocalSessionState=TRUE&useUnicode=TRUE&characterEncoding=utf-8&socketTimeout=30000000&connectTimeout=600000&sessionVariables=ob_query_timeout=60000000000"; + private static String JDBC_PROXY_URL = "jdbc:mysql://" + + ODP_IP + + ":" + + ODP_SQL_PORT + + "/" + + JDBC_DATABASE + + "?rewriteBatchedStatements=TRUE&allowMultiQueries=TRUE&useLocalSessionState=TRUE&useUnicode=TRUE&characterEncoding=utf-8&socketTimeout=30000000&connectTimeout=600000&sessionVariables=ob_query_timeout=60000000000"; private static boolean printDebug = true; private static int SQL_AUDIT_PERSENT = 20; @@ -210,11 +218,15 @@ public class ObTableWeakReadTest { + "WHERE t.table_name = ? AND t.tenant_id = ? AND t.tablet_id = ?;"; private static String SET_SQL_AUDIT_PERSENT_SQL = "SET GLOBAL ob_sql_audit_percentage =?;"; private static String GET_TENANT_ID_SQL = "SELECT tenant_id FROM oceanbase.__all_tenant WHERE tenant_name = ?"; + private static String SET_IDC_SQL = "ALTER PROXYCONFIG SET proxy_idc_name = ?;"; + private static String SET_ROUTE_POLICY_SQL = "ALTER PROXYCONFIG SET proxy_route_policy = ?;"; private Connection tenantConnection = null; private Connection sysConnection = null; + private Connection proxyConnection = null; private org.apache.hadoop.hbase.client.Connection connection = null; private static Connection staticTenantConnection = null; private static Connection staticSysConnection = null; + private static Connection staticProxyConnection = null; private static org.apache.hadoop.hbase.client.Connection staticonnection = null; private static int staticTenantId = 0; private static boolean clear = false; @@ -227,17 +239,19 @@ private static void initObkvConfig(Configuration config, String idc, String rout config.set(HBASE_OCEANBASE_ODP_ADDR, ODP_IP); config.setInt(HBASE_OCEANBASE_ODP_PORT, ODP_PORT); config.set(HBASE_OCEANBASE_DATABASE, ODP_DATABASE); + // 在 ODP 模式下,IDC 和路由策略通过 SQL 设置,不在这里设置 } else { config.set(HBASE_OCEANBASE_PARAM_URL, PARAM_URL); config.set(HBASE_OCEANBASE_SYS_USER_NAME, PROXY_SYS_USER_NAME); config.set(HBASE_OCEANBASE_SYS_PASSWORD, PROXY_SYS_USER_PASSWORD); config.set(HBASE_OCEANBASE_FULL_USER_NAME, FULL_USER_NAME); config.set(HBASE_OCEANBASE_PASSWORD, PASSWORD); + // 在非 ODP 模式下,IDC 和路由策略通过配置设置 + if (idc != null) { + config.set(HBASE_HTABLE_CLIENT_IDC, idc); + } + config.set(HBASE_HTABLE_CLIENT_ROUTE_POLICY, routePolicy); } - if (idc != null) { - config.set(HBASE_HTABLE_CLIENT_IDC, idc); - } - config.set(HBASE_HTABLE_CLIENT_ROUTE_POLICY, routePolicy); } /** @@ -255,11 +269,32 @@ private static Connection getSysConnection() throws SQLException { return DriverManager.getConnection(JDBC_URL, "root@sys", PASSWORD); } + /** + * 获取代理连接 + */ + private static Connection getProxyConnection() throws SQLException { + if (USE_ODP) { + // ODP 连接需要包含集群信息的用户名 + // FULL_USER_NAME 格式: user@tenant#cluster + // 对于代理连接,使用 root@sys#cluster 格式 + String[] parts = FULL_USER_NAME.split("#"); + String clusterName = parts.length > 1 ? parts[1] : ""; + String proxyUserName = PROXY_SYS_USER_NAME + "@sys"; + if (!clusterName.isEmpty()) { + proxyUserName += "#" + clusterName; + } + return DriverManager.getConnection(JDBC_PROXY_URL, proxyUserName, PROXY_SYS_USER_PASSWORD); + } else { + return null; + } + } + @org.junit.BeforeClass public static void beforeClass() throws Exception { // 所有测试用例执行前创建表和连接(只执行一次) staticTenantConnection = getConnection(); staticSysConnection = getSysConnection(); + staticProxyConnection = getProxyConnection(); staticTenantId = getTenantId(staticSysConnection); createTable(staticTenantConnection); setSqlAuditPersent(staticTenantConnection, SQL_AUDIT_PERSENT); @@ -276,6 +311,7 @@ public static void afterClass() throws Exception { public void setup() throws Exception { tenantConnection = staticTenantConnection; sysConnection = staticSysConnection; + proxyConnection = staticProxyConnection; tenant_id = staticTenantId; connection = staticonnection; } @@ -510,6 +546,34 @@ private void debugPrint(String format, Object... args) { } } + private void setIdc(Configuration config, String idc) throws Exception { + if (USE_ODP) { + if (proxyConnection != null) { + PreparedStatement statement = proxyConnection.prepareStatement(SET_IDC_SQL); + statement.setString(1, idc); + statement.execute(); + statement.close(); + } + } else { + if (idc != null) { + config.set(HBASE_HTABLE_CLIENT_IDC, idc); + } + } + } + + private void setRoutePolicy(Configuration config, String routePolicy) throws Exception { + if (USE_ODP) { + if (proxyConnection != null) { + PreparedStatement statement = proxyConnection.prepareStatement(SET_ROUTE_POLICY_SQL); + statement.setString(1, routePolicy); + statement.execute(); + statement.close(); + } + } else { + config.set(HBASE_HTABLE_CLIENT_ROUTE_POLICY, routePolicy); + } + } + /* * 测试场景:用户正常使用场景,使用get接口进行指定IDC读 * 测试预期:发到对应的IDC上进行读取 @@ -518,6 +582,11 @@ private void debugPrint(String format, Object... args) { public void testIdcGet1() throws Exception { Configuration config = HBaseConfiguration.create(); initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + // 在 ODP 模式下,需要在创建连接前通过 SQL 设置 IDC 和路由策略 + if (USE_ODP) { + setIdc(config, IDC2); + setRoutePolicy(config, FOLLOW_FIRST_ROUTE_POLICY); + } org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory .createConnection(config); Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); From 6130bd7b42a595bf0b1c1d298944aa58dfc01cd6 Mon Sep 17 00:00:00 2001 From: JackShi148 Date: Tue, 9 Dec 2025 18:11:09 +0800 Subject: [PATCH 11/15] change domain to keep the same as 1x --- .../java/com/alipay/oceanbase/hbase/util/OHMetrics.java | 2 +- .../com/alipay/oceanbase/hbase/util/OHMetricsTracker.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java index 6cc41fa7..a2bfb42e 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java @@ -32,7 +32,7 @@ public OHMetrics(String metricsName) { opType); } this.reporter = JmxReporter.forRegistry(this.registry) - .inDomain("com.oceanbase.hbase.metrics") + .inDomain("com.alipay.oceanbase.hbase.metrics") .build(); this.reporter.start(); scheduler.scheduleWithFixedDelay(this::updateMetrics, 0, 10, TimeUnit.SECONDS); diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java index dda0929f..485544f2 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java @@ -18,13 +18,13 @@ public OHMetricsTracker(MetricRegistry registry, String metricsName, OHOperation String typeName = opType.name(); this.opType = opType; this.latencyHistogram = registry.timer( - name(OHMetrics.class, typeName, "latencyHistogram", metricsName)); + name(metricsName, typeName, "latencyHistogram")); this.failedOpCounter = registry.meter( - name(OHMetrics.class, typeName, "failedOpCounter", metricsName)); + name(metricsName, typeName, "failedOpCounter")); this.totalSingleOpCount = registry.counter( - name(OHMetrics.class, typeName, "totalSingleOpCount", metricsName)); + name(metricsName, typeName, "totalSingleOpCount")); this.totalRuntime = registry.counter( - name(OHMetrics.class, typeName, "totalRuntime", metricsName)); + name(metricsName, typeName, "totalRuntime")); } public OHOperationType getOpType() { From 8c3ebff8d969a850a1848b44110545eec54ac06a Mon Sep 17 00:00:00 2001 From: WeiXinChan Date: Wed, 10 Dec 2025 11:28:16 +0800 Subject: [PATCH 12/15] fix weak read bug in batch get --- .../com/alipay/oceanbase/hbase/OHTable.java | 28 +- .../hbase/util/ObTableClientManager.java | 3 - .../oceanbase/hbase/ObTableWeakReadTest.java | 504 +++++++++++++++++- 3 files changed, 513 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index 181e456f..63a03ae7 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -2097,11 +2097,13 @@ private ObTableQuery buildObTableQuery(final Get get, Collection columnQ /** * Check if the Get or Scan operation is configured for weak read. + * If the query does not have HBASE_HTABLE_READ_CONSISTENCY attribute set, + * it will fall back to the global configuration. * * @param query the Get or Scan object to check * @return true if weak read is enabled, false otherwise */ - public static boolean isWeakRead(Object query) { + public boolean isWeakRead(Object query) { if (query == null) { return false; } @@ -2113,11 +2115,16 @@ public static boolean isWeakRead(Object query) { } else { return false; } + String consistencyStr; if (consistency == null) { - return false; + // fall back to global configuration + consistencyStr = configuration.get(HBASE_HTABLE_READ_CONSISTENCY); + if (consistencyStr == null) { + return false; + } + } else { + consistencyStr = Bytes.toString(consistency); } - String consistencyStr = Bytes.toString(consistency); - System.out.println("consistencyStr: " + consistencyStr); return "weak".equalsIgnoreCase(consistencyStr); } @@ -2343,10 +2350,14 @@ private BatchOperation buildBatchOperation(String tableName, List BatchOperation batch = obTableClient.batchOperation(tableName); int posInList = -1; int singleOpResultNum; + int getOperationNum = 0; + // allGetIsWeakRead is initialized to true, and set to false if any Get is not weak read + boolean allGetIsWeakRead = true; for (Row row : actions) { singleOpResultNum = 0; posInList++; if (row instanceof Get) { + getOperationNum++; if (!ObGlobal.isHBaseBatchGetSupport()) { throw new FeatureNotSupportedException("server does not support batch get"); } @@ -2381,8 +2392,9 @@ private BatchOperation buildBatchOperation(String tableName, List ObTableClientQueryImpl query = new ObTableClientQueryImpl(tableName, obTableQuery, obTableClient); try { query.setRowKey(row(colVal("K", Bytes.toString(get.getRow())), colVal("Q", null), colVal("T", Integer.MAX_VALUE))); - if (isWeakRead(get)) { - batch.setReadConsistency(ObReadConsistency.WEAK); + // if any Get is not weak read, set allGetIsWeakRead to false + if (!isWeakRead(get)) { + allGetIsWeakRead = false; } } catch (Exception e) { logger.error("unexpected error occurs when set row key", e); @@ -2457,6 +2469,10 @@ private BatchOperation buildBatchOperation(String tableName, List } resultMapSingleOp.add(singleOpResultNum); } + // only set weak read consistency when all operations are Get and all Get operations are weak read + if (getOperationNum == actions.size() && allGetIsWeakRead) { + batch.setReadConsistency(ObReadConsistency.WEAK); + } batch.setEntityType(ObTableEntityType.HKV); batch.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); batch.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java b/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java index ac1cd357..56feb224 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java +++ b/src/main/java/com/alipay/oceanbase/hbase/util/ObTableClientManager.java @@ -126,9 +126,6 @@ public static ObTableClient getOrCreateObTableClient(ObTableClientKey obTableCli if (connectionConfig.getRoutePolicy() != null) { obTableClient.setRoutePolicy(ObRoutePolicy.getByName(connectionConfig.getRoutePolicy())); } - if (connectionConfig.getGlobalWeakRead() != null) { - obTableClient.setReadConsistency(ObReadConsistency.getByName(connectionConfig.getGlobalWeakRead())); - } obTableClient.init(); OB_TABLE_CLIENT_INSTANCE.put(obTableClientKey, obTableClient); } diff --git a/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java b/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java index 36a65723..301880f7 100644 --- a/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java +++ b/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java @@ -160,18 +160,18 @@ public String toString() { */ public class ObTableWeakReadTest { // 测试配置常量 - private static String FULL_USER_NAME = ""; - private static String PARAM_URL = ""; - private static String PASSWORD = ""; - private static String PROXY_SYS_USER_NAME = "root"; - private static String PROXY_SYS_USER_PASSWORD = ""; + private static String FULL_USER_NAME = ""; + private static String PARAM_URL = ""; + private static String PASSWORD = ""; + private static String PROXY_SYS_USER_NAME = ""; + private static String PROXY_SYS_USER_PASSWORD = ""; private static boolean USE_ODP = false; private static String ODP_IP = "ip-addr"; private static int ODP_PORT = 0; private static int ODP_SQL_PORT = 0; private static String ODP_DATABASE = "database-name"; - private static String JDBC_IP = ""; - private static String JDBC_PORT = ""; + private static String JDBC_IP = ""; + private static String JDBC_PORT = ""; private static String JDBC_DATABASE = "test"; private static String JDBC_URL = "jdbc:mysql://" + JDBC_IP @@ -2098,16 +2098,18 @@ public void testIdcBatchGet1() throws Exception { setZoneIdc(ZONE3, IDC3); // 3. 获取数据 List gets = new ArrayList<>(); - Get get = new Get(rowkey.getBytes()); - get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); - get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); - gets.add(get); - gets.add(get); + Get get1 = new Get(rowkey.getBytes()); + get1.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get1.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Get get2 = new Get((rowkey + "1").getBytes()); + get2.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get2.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get1); + gets.add(get2); Result[] res = table.get(gets); Assert.assertNotNull(res); Assert.assertEquals(2, res.length); Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); - Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); // 4. 查询 sql audit,确定读请求发到哪个节点和分区上 SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); @@ -2796,4 +2798,480 @@ public void testRoutePolicyBatchGet5() throws Exception { debugPrint("readReplica: %s", readReplica.toString()); Assert.assertTrue(readReplica.isFollower()); } + + /* + * 测试场景:全局配置为weak,语句级别设置为strong(语句级别优先级大于全局级别) + * 测试预期:语句级别的strong优先生效,读leader + */ + @Test + public void testGlobalConfigOverride1() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "weak"); // 设置全局弱一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据,语句级别设置为strong,优先级大于全局weak + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:语句级别strong优先生效,读leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:全局配置为strong,语句级别设置为weak(语句级别优先级大于全局级别) + * 测试预期:语句级别的weak优先生效,读follower + */ + @Test + public void testGlobalConfigOverride2() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "strong"); // 设置全局强一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. 获取数据,语句级别设置为weak,优先级大于全局strong + Get get = new Get(rowkey.getBytes()); + get.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Result result = table.get(get); + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:语句级别weak优先生效,读follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:全局配置为weak,Scan操作不设置属性 + * 测试预期:使用全局配置weak,读follower + */ + @Test + public void testGlobalConfigScan() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "weak"); // 设置全局弱一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey = getRandomRowkString(); + insertData(table, rowkey); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. Scan不设置READ_CONSISTENCY属性,使用全局配置 + Scan scan = new Scan(); + scan.withStartRow(rowkey.getBytes()); + // 不设置 READ_CONSISTENCY 属性 + scan.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + ResultScanner resultScanner = table.getScanner(scan); + Result result; + while ((result = resultScanner.next()) != null) { + Assert.assertNotNull(result); + Assert.assertNotEquals(true, result.isEmpty()); + byte[] value = result.getValue(FAMILY_NAME.getBytes(), "c2".getBytes()); + Assert.assertEquals("c2_val", Bytes.toString(value)); + } + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:应该使用全局weak配置,读follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:batch get中部分语句设置weak,部分语句设置strong + * 测试预期:只要有一个语句不是weak,整个batch就不设置弱读一致性,读leader + * 注意:语句级别的配置项优先级大于全局级别 + */ + @Test + public void testBatchGetMixedConsistency1() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey1 = getRandomRowkString(); + String rowkey2 = rowkey1 + "_2"; + insertData(table, rowkey1); + insertData(table, rowkey2); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. batch get:get1设置weak,get2设置strong + List gets = new ArrayList<>(); + Get get1 = new Get(rowkey1.getBytes()); + get1.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get1.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Get get2 = new Get(rowkey2.getBytes()); + get2.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get2.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get1); + gets.add(get2); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(2, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey1, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:由于存在非weak读的语句,整个batch不设置弱读一致性,读leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:batch get中所有语句都设置weak + * 测试预期:所有语句都是weak,整个batch使用weak一致性,读follower + */ + @Test + public void testBatchGetMixedConsistency2() throws Exception { + Configuration config = HBaseConfiguration.create(); + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey1 = getRandomRowkString(); + String rowkey2 = rowkey1 + "_2"; + insertData(table, rowkey1); + insertData(table, rowkey2); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. batch get:所有get都设置weak + List gets = new ArrayList<>(); + Get get1 = new Get(rowkey1.getBytes()); + get1.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get1.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Get get2 = new Get(rowkey2.getBytes()); + get2.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get2.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get1); + gets.add(get2); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(2, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey1, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:所有都是weak,应该读follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:batch get中部分语句设置weak,部分语句不设置属性(fallback到全局weak配置) + * 测试预期:语句级别优先,不设置属性的语句使用全局weak配置,所有语句都是weak,整个batch使用weak一致性 + */ + @Test + public void testBatchGetMixedConsistency3() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "weak"); // 设置全局弱一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey1 = getRandomRowkString(); + String rowkey2 = rowkey1 + "_2"; + insertData(table, rowkey1); + insertData(table, rowkey2); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. batch get:get1设置weak,get2不设置属性(使用全局配置) + List gets = new ArrayList<>(); + Get get1 = new Get(rowkey1.getBytes()); + get1.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get1.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Get get2 = new Get(rowkey2.getBytes()); + // get2不设置READ_CONSISTENCY属性,使用全局weak配置 + get2.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get1); + gets.add(get2); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(2, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey1, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:都是weak,应该读follower + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isFollower()); + Assert.assertEquals(IDC2, readReplica.getIdc()); + } + + /* + * 测试场景:batch get中部分语句不设置属性(fallback到全局weak配置),部分语句设置strong + * 测试预期:语句级别优先,存在非weak的语句,整个batch不设置弱读一致性,读leader + */ + @Test + public void testBatchGetMixedConsistency4() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "weak"); // 设置全局弱一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey1 = getRandomRowkString(); + String rowkey2 = rowkey1 + "_2"; + insertData(table, rowkey1); + insertData(table, rowkey2); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. batch get:get1不设置属性(使用全局weak配置),get2设置strong + List gets = new ArrayList<>(); + Get get1 = new Get(rowkey1.getBytes()); + // get1不设置READ_CONSISTENCY属性,使用全局weak配置 + get1.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Get get2 = new Get(rowkey2.getBytes()); + get2.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get2.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get1); + gets.add(get2); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(2, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey1, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:由于存在非weak的语句,整个batch不设置弱读一致性,读leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:全局配置为strong,batch get中所有语句都不设置属性 + * 测试预期:所有语句都fallback到全局strong配置,整个batch不设置弱读一致性,读leader + */ + @Test + public void testBatchGetGlobalStrong() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "strong"); // 设置全局强一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey1 = getRandomRowkString(); + String rowkey2 = rowkey1 + "_2"; + insertData(table, rowkey1); + insertData(table, rowkey2); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. batch get:所有get都不设置属性,使用全局strong配置 + List gets = new ArrayList<>(); + Get get1 = new Get(rowkey1.getBytes()); + get1.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Get get2 = new Get(rowkey2.getBytes()); + get2.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get1); + gets.add(get2); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(2, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey1, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:使用全局strong配置,应该读leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:全局配置为weak,batch get中3个语句:1个设置weak,1个设置strong,1个不设置(fallback到全局weak) + * 测试预期:语句级别优先,存在非weak的语句(strong),整个batch不设置弱读一致性,读leader + */ + @Test + public void testBatchGetMixedConsistency5() throws Exception { + Configuration config = HBaseConfiguration.create(); + config.set(HBASE_HTABLE_READ_CONSISTENCY, "weak"); // 设置全局弱一致性读 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey1 = getRandomRowkString(); + String rowkey2 = rowkey1 + "_2"; + String rowkey3 = rowkey1 + "_3"; + insertData(table, rowkey1); + insertData(table, rowkey2); + insertData(table, rowkey3); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. batch get:get1设置weak,get2设置strong,get3不设置(使用全局weak) + List gets = new ArrayList<>(); + Get get1 = new Get(rowkey1.getBytes()); + get1.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "weak".getBytes()); + get1.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Get get2 = new Get(rowkey2.getBytes()); + get2.setAttribute(HBASE_HTABLE_READ_CONSISTENCY, "strong".getBytes()); + get2.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Get get3 = new Get(rowkey3.getBytes()); + // get3不设置READ_CONSISTENCY属性,使用全局weak配置 + get3.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get1); + gets.add(get2); + gets.add(get3); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(3, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[2].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey1, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:由于存在非weak的语句,整个batch不设置弱读一致性,读leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } + + /* + * 测试场景:不设置全局配置,batch get中所有语句都不设置属性 + * 测试预期:语句级别和全局配置都未设置,使用默认strong一致性,整个batch不设置弱读一致性,读leader + */ + @Test + public void testBatchGetNoConfig() throws Exception { + Configuration config = HBaseConfiguration.create(); + // 不设置全局READ_CONSISTENCY配置 + initObkvConfig(config, IDC2, FOLLOW_FIRST_ROUTE_POLICY); + org.apache.hadoop.hbase.client.Connection hbaseConnection = ConnectionFactory.createConnection(config); + Table table = hbaseConnection.getTable(TableName.valueOf(TABLE_NAME)); + // 1. 准备数据 + String rowkey1 = getRandomRowkString(); + String rowkey2 = rowkey1 + "_2"; + insertData(table, rowkey1); + insertData(table, rowkey2); + Thread.sleep(1000); + // 2. 设置 idc + setZoneIdc(ZONE1, IDC1); + setZoneIdc(ZONE2, IDC2); + setZoneIdc(ZONE3, IDC3); + // 3. batch get:所有get都不设置属性 + List gets = new ArrayList<>(); + Get get1 = new Get(rowkey1.getBytes()); + get1.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + Get get2 = new Get(rowkey2.getBytes()); + get2.addColumn(FAMILY_NAME.getBytes(), "c2".getBytes()); + gets.add(get1); + gets.add(get2); + Result[] res = table.get(gets); + Assert.assertNotNull(res); + Assert.assertEquals(2, res.length); + Assert.assertEquals("c2_val", Bytes.toString(res[0].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + Assert.assertEquals("c2_val", Bytes.toString(res[1].getValue(FAMILY_NAME.getBytes(), "c2".getBytes()))); + // 4. 查询 sql audit + SqlAuditResult sqlAuditResult = getServerBySqlAudit(rowkey1, "scan"); + debugPrint("sqlAuditResult: %s", sqlAuditResult.toString()); + // 5. 查询分区的位置信息 + PartitionLocation partitionLocation = getPartitionLocation(sqlAuditResult.tabletId); + debugPrint("partitionLocation: %s", partitionLocation.toString()); + // 6. 校验:默认应该是strong,读leader + ReplicaLocation readReplica = partitionLocation.getReplicaBySvrAddr(sqlAuditResult.svrIp, + sqlAuditResult.svrPort); + debugPrint("readReplica: %s", readReplica.toString()); + Assert.assertTrue(readReplica.isLeader()); + } } From 610c03e01f8860b47f507ebc43515d8d42f5f0b1 Mon Sep 17 00:00:00 2001 From: JackShi148 Date: Tue, 9 Dec 2025 19:56:34 +0800 Subject: [PATCH 13/15] fix jmx test bug --- .../java/com/alipay/oceanbase/hbase/OHTable.java | 2 ++ .../com/alipay/oceanbase/hbase/OHTableClient.java | 2 +- .../hbase/{util => metrics}/MetricsExporter.java | 2 +- .../hbase/{util => metrics}/MetricsImporter.java | 2 +- .../oceanbase/hbase/{util => metrics}/OHMetrics.java | 3 ++- .../hbase/{util => metrics}/OHMetricsTracker.java | 2 +- .../oceanbase/hbase/result/ClientStreamScanner.java | 4 ++-- .../com/alipay/oceanbase/hbase/JMXMetricsTest.java | 4 ++-- .../com/alipay/oceanbase/hbase/OHMetricsTest.java | 4 ++-- .../oceanbase/hbase/util/JMXMetricsTestHelper.java | 12 ++++++------ 10 files changed, 20 insertions(+), 17 deletions(-) rename src/main/java/com/alipay/oceanbase/hbase/{util => metrics}/MetricsExporter.java (99%) rename src/main/java/com/alipay/oceanbase/hbase/{util => metrics}/MetricsImporter.java (94%) rename src/main/java/com/alipay/oceanbase/hbase/{util => metrics}/OHMetrics.java (97%) rename src/main/java/com/alipay/oceanbase/hbase/{util => metrics}/OHMetricsTracker.java (98%) diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index 181e456f..0b9ada6e 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -21,6 +21,8 @@ import com.alipay.oceanbase.hbase.exception.OperationTimeoutException; import com.alipay.oceanbase.hbase.execute.ServerCallable; import com.alipay.oceanbase.hbase.filter.HBaseFilterUtils; +import com.alipay.oceanbase.hbase.metrics.MetricsImporter; +import com.alipay.oceanbase.hbase.metrics.OHMetrics; import com.alipay.oceanbase.hbase.result.ClientStreamScanner; import com.alipay.oceanbase.hbase.util.*; import com.alipay.oceanbase.rpc.ObGlobal; diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTableClient.java b/src/main/java/com/alipay/oceanbase/hbase/OHTableClient.java index 7a113b82..660c491e 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTableClient.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTableClient.java @@ -18,7 +18,7 @@ package com.alipay.oceanbase.hbase; import com.alipay.oceanbase.hbase.core.Lifecycle; -import com.alipay.oceanbase.hbase.util.OHMetrics; +import com.alipay.oceanbase.hbase.metrics.OHMetrics; import com.google.protobuf.Descriptors; import com.google.protobuf.Message; import com.google.protobuf.Service; diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java b/src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsExporter.java similarity index 99% rename from src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java rename to src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsExporter.java index c32b15d2..4898c029 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsExporter.java +++ b/src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsExporter.java @@ -1,4 +1,4 @@ -package com.alipay.oceanbase.hbase.util; +package com.alipay.oceanbase.hbase.metrics; import com.codahale.metrics.Snapshot; import com.codahale.metrics.Timer; diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java b/src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsImporter.java similarity index 94% rename from src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java rename to src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsImporter.java index d442fccf..4230bc0a 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/MetricsImporter.java +++ b/src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsImporter.java @@ -1,4 +1,4 @@ -package com.alipay.oceanbase.hbase.util; +package com.alipay.oceanbase.hbase.metrics; public class MetricsImporter { private boolean isFailedOp; diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java b/src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetrics.java similarity index 97% rename from src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java rename to src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetrics.java index a2bfb42e..c3afc7d6 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetrics.java +++ b/src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetrics.java @@ -1,5 +1,6 @@ -package com.alipay.oceanbase.hbase.util; +package com.alipay.oceanbase.hbase.metrics; +import com.alipay.oceanbase.hbase.util.TableHBaseLoggerFactory; import com.alipay.oceanbase.rpc.location.model.partition.ObPair; import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; import com.codahale.metrics.JmxReporter; diff --git a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java b/src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetricsTracker.java similarity index 98% rename from src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java rename to src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetricsTracker.java index 485544f2..daf92818 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/util/OHMetricsTracker.java +++ b/src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetricsTracker.java @@ -1,4 +1,4 @@ -package com.alipay.oceanbase.hbase.util; +package com.alipay.oceanbase.hbase.metrics; import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; import com.codahale.metrics.*; diff --git a/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java b/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java index 5d588d87..cfc50270 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java +++ b/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java @@ -17,9 +17,9 @@ package com.alipay.oceanbase.hbase.result; -import com.alipay.oceanbase.hbase.util.MetricsImporter; +import com.alipay.oceanbase.hbase.metrics.MetricsImporter; import com.alipay.oceanbase.hbase.util.OHBaseFuncUtils; -import com.alipay.oceanbase.hbase.util.OHMetrics; +import com.alipay.oceanbase.hbase.metrics.OHMetrics; import com.alipay.oceanbase.hbase.util.TableHBaseLoggerFactory; import com.alipay.oceanbase.rpc.location.model.partition.ObPair; import com.alipay.oceanbase.rpc.protocol.payload.impl.ObObj; diff --git a/src/test/java/com/alipay/oceanbase/hbase/JMXMetricsTest.java b/src/test/java/com/alipay/oceanbase/hbase/JMXMetricsTest.java index 76be9286..098c367e 100644 --- a/src/test/java/com/alipay/oceanbase/hbase/JMXMetricsTest.java +++ b/src/test/java/com/alipay/oceanbase/hbase/JMXMetricsTest.java @@ -1,8 +1,8 @@ package com.alipay.oceanbase.hbase; import com.alipay.oceanbase.hbase.util.JMXMetricsTestHelper; -import com.alipay.oceanbase.hbase.util.MetricsExporter; -import com.alipay.oceanbase.hbase.util.OHMetrics; +import com.alipay.oceanbase.hbase.metrics.MetricsExporter; +import com.alipay.oceanbase.hbase.metrics.OHMetrics; import com.alipay.oceanbase.hbase.util.ObHTableTestUtil; import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; import org.apache.hadoop.conf.Configuration; diff --git a/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java b/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java index 2046ea72..2d17522d 100644 --- a/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java +++ b/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java @@ -1,7 +1,7 @@ package com.alipay.oceanbase.hbase; -import com.alipay.oceanbase.hbase.util.MetricsExporter; -import com.alipay.oceanbase.hbase.util.OHMetrics; +import com.alipay.oceanbase.hbase.metrics.MetricsExporter; +import com.alipay.oceanbase.hbase.metrics.OHMetrics; import com.alipay.oceanbase.hbase.util.ObHTableTestUtil; import com.alipay.oceanbase.rpc.exception.ObTableUnexpectedException; import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; diff --git a/src/test/java/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java b/src/test/java/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java index 36ed5702..0244c414 100644 --- a/src/test/java/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java +++ b/src/test/java/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java @@ -1,5 +1,6 @@ package com.alipay.oceanbase.hbase.util; +import com.alipay.oceanbase.hbase.metrics.OHMetrics; import com.alipay.oceanbase.rpc.protocol.payload.impl.execute.OHOperationType; import org.junit.Assert; @@ -12,7 +13,7 @@ */ public class JMXMetricsTestHelper { - private static final String JMX_DOMAIN = "com.oceanbase.hbase.metrics"; + private static final String JMX_DOMAIN = "com.alipay.oceanbase.hbase.metrics"; private final MBeanServer mBeanServer; public JMXMetricsTestHelper() { @@ -31,12 +32,11 @@ public ObjectName getObjectName(OHOperationType opType, String metricsName, String attributeName) { try { // the format of JMX name: domain:name=metricName - // example: com.alipay.oceanbase.hbase.util.OHMetrics.PUT.latencyHistogram.metricsName - String name = String.format("%s.%s.%s.%s", - OHMetrics.class.getName(), + // example: test_multi_cf.test.PUT.latencyHistogram + String name = String.format("%s.%s.%s", + metricsName, opType.name(), - attributeName, - metricsName); + attributeName); String objectNameStr = String.format("%s:name=%s", JMX_DOMAIN, name); From 5987415fc3b017b9a9eee239e2f1a2778ce7ac4d Mon Sep 17 00:00:00 2001 From: maochongxin Date: Thu, 11 Dec 2025 15:16:50 +0800 Subject: [PATCH 14/15] Add client configuration to control hot key optimization enablement --- .../com/alipay/oceanbase/hbase/OHTable.java | 16 ++++ .../hbase/constants/OHConstants.java | 11 +++ .../hbase/OHTableGetOptimizeTest.java | 78 +++++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index c1607044..81fe87d7 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -2075,6 +2075,20 @@ private ObTableQuery buildObTableQuery(ObHTableFilter filter, final Scan scan) { obTableQuery.setScanRangeColumns("K", "Q", "T"); byte[] hotOnly = scan.getAttribute(HBASE_HTABLE_QUERY_HOT_ONLY); obTableQuery.setHotOnly(hotOnly != null && Arrays.equals(hotOnly, "true".getBytes())); + // HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE is a statement-level setting, while HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE_GLOBAL is a global setting. + // The statement-level setting takes precedence over the global setting. + // If the statement-level setting is not configured, use the global setting. + // If the statement-level setting is configured, use the statement-level setting. + boolean hotKeyGetOptimizeEnableBool = false; + byte[] hotKeyGetOptimizeEnable = scan.getAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE); + if (hotKeyGetOptimizeEnable == null) { + boolean hotKeyGetOptimizeEnableGlobal = configuration.getBoolean(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE_GLOBAL, false); + hotKeyGetOptimizeEnableBool = hotKeyGetOptimizeEnableGlobal; + } else { + hotKeyGetOptimizeEnableBool = Boolean.parseBoolean(Bytes.toString(hotKeyGetOptimizeEnable)); + } + + obTableQuery.setGetOptimized(hotKeyGetOptimizeEnableBool); return obTableQuery; } @@ -2094,6 +2108,8 @@ private ObTableQuery buildObTableQuery(final Get get, Collection columnQ obTableQuery.setScanRangeColumns("K", "Q", "T"); byte[] hotOnly = get.getAttribute(HBASE_HTABLE_QUERY_HOT_ONLY); obTableQuery.setHotOnly(hotOnly != null && Arrays.equals(hotOnly, "true".getBytes())); + byte[] hotKeyGetOptimizeEnable = get.getAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE); + obTableQuery.setGetOptimized(hotKeyGetOptimizeEnable != null && Arrays.equals(hotKeyGetOptimizeEnable, "true".getBytes())); return obTableQuery; } diff --git a/src/main/java/com/alipay/oceanbase/hbase/constants/OHConstants.java b/src/main/java/com/alipay/oceanbase/hbase/constants/OHConstants.java index 8260e2fe..a0350c61 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/constants/OHConstants.java +++ b/src/main/java/com/alipay/oceanbase/hbase/constants/OHConstants.java @@ -134,6 +134,17 @@ public final class OHConstants { */ public static final String HBASE_HTABLE_READ_CONSISTENCY = "hbase.htable.read.consistency"; + + /** + * use to specify whether to enable the hotkey get optimize when performing a query. + */ + public static final String HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE = "hbase.htable.hotkey.get.optimize.enable"; + + /** + * 开启后不指定时间戳的读写都将以此时间戳进行写入和查询, 默认为兼容原行为 + */ + public static final String HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE_GLOBAL = "hbase.htable.hotkey.get.optimize.enable.global"; + /** * use to specify the idc when performing a query. */ diff --git a/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java b/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java index 518027a1..69ef4a03 100644 --- a/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java +++ b/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java @@ -27,6 +27,8 @@ import java.sql.SQLException; import java.util.*; +import static com.alipay.oceanbase.hbase.constants.OHConstants.HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE; +import static com.alipay.oceanbase.hbase.constants.OHConstants.HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE_GLOBAL; import static org.apache.hadoop.hbase.util.Bytes.toBytes; /** @@ -96,6 +98,7 @@ public void testGetOptimizeWithMaxVersion1() throws Exception { // Get with single column Get get = new Get(toBytes(key1)); get.addColumn(family1.getBytes(), column1.getBytes()); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -105,6 +108,7 @@ public void testGetOptimizeWithMaxVersion1() throws Exception { get = new Get(toBytes(key1)); get.addColumn(family1.getBytes(), column1.getBytes()); get.setMaxVersions(10); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -126,6 +130,7 @@ public void testGetOptimizeWithMaxVersion1() throws Exception { get = new Get(toBytes(key2)); get.addColumn(family2.getBytes(), column1.getBytes()); get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -135,6 +140,7 @@ public void testGetOptimizeWithMaxVersion1() throws Exception { get = new Get(toBytes(key2)); get.addColumn(family2.getBytes(), column1.getBytes()); get.setMaxVersions(10); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertTrue(r.rawCells().length >= 3); Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -178,6 +184,7 @@ public void testGetOptimizeSingleColumn() throws Exception { Get get = new Get(toBytes(key)); get.addColumn(family.getBytes(), col1.getBytes()); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_c1_v3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -196,6 +203,7 @@ public void testGetOptimizeSingleColumn() throws Exception { get = new Get(toBytes(key)); get.addColumn(family.getBytes(), col2.getBytes()); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r2 = hTable.get(get); Assert.assertEquals(1, r2.rawCells().length); Assert.assertEquals("value_c2_v2", Bytes.toString(r2.rawCells()[0].getValueArray(), r2.rawCells()[0].getValueOffset(), r2.rawCells()[0].getValueLength())); @@ -244,6 +252,10 @@ public void testGetOptimizeBatchGet() throws Exception { gets.add(get); } + for (Get get : gets) { + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); + } + Result[] results = hTable.get(gets); Assert.assertEquals(3, results.length); for (int i = 0; i < results.length; i++) { @@ -282,6 +294,7 @@ public void testGetOptimizeExplicitMaxVersion1() throws Exception { Get get = new Get(toBytes(key)); get.addColumn(family.getBytes(), column.getBytes()); get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -290,6 +303,7 @@ public void testGetOptimizeExplicitMaxVersion1() throws Exception { get = new Get(toBytes(key)); get.addColumn(family.getBytes(), column.getBytes()); get.setMaxVersions(3); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(3, r.rawCells().length); Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -299,6 +313,7 @@ public void testGetOptimizeExplicitMaxVersion1() throws Exception { // Get with default MaxVersions get = new Get(toBytes(key)); get.addColumn(family.getBytes(), column.getBytes()); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -341,6 +356,7 @@ public void testGetOptimizeWithTimeRange() throws Exception { Get get = new Get(toBytes(key)); get.addColumn(family.getBytes(), column.getBytes()); get.setTimeRange(t1, t2 + 1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); // With MaxVersions=1 at table level, should get only 1 version Assert.assertTrue(r.rawCells().length >= 1); @@ -352,6 +368,7 @@ public void testGetOptimizeWithTimeRange() throws Exception { get.addColumn(family.getBytes(), column.getBytes()); get.setTimeRange(t1, t3 + 1); get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_t3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -398,6 +415,7 @@ public void testGetOptimizeWithSpecificTimestamp() throws Exception { Get get = new Get(toBytes(key)); get.addColumn(family1.getBytes(), column.getBytes()); get.setTimeStamp(t2); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_t2", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -407,6 +425,7 @@ public void testGetOptimizeWithSpecificTimestamp() throws Exception { get = new Get(toBytes(key)); get.addColumn(family1.getBytes(), column.getBytes()); get.setTimeStamp(t1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_t1", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -416,6 +435,7 @@ public void testGetOptimizeWithSpecificTimestamp() throws Exception { get = new Get(toBytes(key)); get.addColumn(family1.getBytes(), column.getBytes()); get.setTimeStamp(t3); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_t3", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -440,6 +460,7 @@ public void testGetOptimizeWithSpecificTimestamp() throws Exception { get.addColumn(family2.getBytes(), column.getBytes()); get.setTimeStamp(t2); get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value2_t2", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -450,6 +471,7 @@ public void testGetOptimizeWithSpecificTimestamp() throws Exception { get = new Get(toBytes(key2)); get.addColumn(family2.getBytes(), column.getBytes()); get.setTimeStamp(nonExistentTs); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(0, r.rawCells().length); } @@ -484,6 +506,7 @@ public void testGetOptimizeWithSpecificTimestampMultiVersions() throws Exception Get get = new Get(toBytes(key)); get.addColumn(family.getBytes(), column.getBytes()); get.setTimeStamp(timestamps[i]); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_" + i, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -495,6 +518,7 @@ public void testGetOptimizeWithSpecificTimestampMultiVersions() throws Exception get.addColumn(family.getBytes(), column.getBytes()); get.setTimeStamp(timestamps[2]); get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_2", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -505,6 +529,7 @@ public void testGetOptimizeWithSpecificTimestampMultiVersions() throws Exception get = new Get(toBytes(key)); get.addColumn(family.getBytes(), column.getBytes()); get.setTimeStamp(betweenTs); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(0, r.rawCells().length); } @@ -573,6 +598,7 @@ public void testGetOptimizeWithMultipleQualifiersOnMaxVersion1Table() throws Exc get.addColumn(family.getBytes(), col1.getBytes()); get.addColumn(family.getBytes(), col2.getBytes()); get.addColumn(family.getBytes(), col3.getBytes()); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(3, r.rawCells().length); @@ -628,6 +654,7 @@ public void testGetOptimizeWithMultipleQualifiersAndExplicitMaxVersion1() throws get.addColumn(family.getBytes(), col3.getBytes()); get.addColumn(family.getBytes(), col4.getBytes()); get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(4, r.rawCells().length); @@ -649,6 +676,7 @@ public void testGetOptimizeWithMultipleQualifiersAndExplicitMaxVersion1() throws get.addColumn(family.getBytes(), col1.getBytes()); get.addColumn(family.getBytes(), col2.getBytes()); get.setMaxVersions(3); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(6, r.rawCells().length); @@ -702,6 +730,7 @@ public void testGetOptimizeWithMultipleQualifiersAndTimestamp() throws Exception get.addColumn(family.getBytes(), col2.getBytes()); get.addColumn(family.getBytes(), col3.getBytes()); get.setTimeStamp(t2); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(3, r.rawCells().length); @@ -750,6 +779,7 @@ public void testTableMaxVersionPrecedence() throws Exception { Get get = new Get(toBytes(key)); get.addColumn(family.getBytes(), col.getBytes()); get.setMaxVersions(3); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -758,6 +788,7 @@ public void testTableMaxVersionPrecedence() throws Exception { get = new Get(toBytes(key)); get.addColumn(family.getBytes(), col.getBytes()); get.setMaxVersions(5); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -766,6 +797,7 @@ public void testTableMaxVersionPrecedence() throws Exception { get = new Get(toBytes(key)); get.addColumn(family.getBytes(), col.getBytes()); get.setMaxVersions(10); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); r = hTable.get(get); Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); @@ -806,6 +838,7 @@ public void testTableMaxVersionPrecedenceWithMultipleQualifiers() throws Excepti get.addColumn(family.getBytes(), col2.getBytes()); get.addColumn(family.getBytes(), col3.getBytes()); get.setMaxVersions(3); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(3, r.rawCells().length); @@ -901,6 +934,7 @@ public void testGetOptimizeWithoutQualifierAndExplicitMaxVersion1() throws Excep Get get = new Get(toBytes(key)); get.addFamily(family.getBytes()); get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(4, r.rawCells().length); @@ -961,6 +995,7 @@ public void testGetOptimizeWithoutQualifierAndTimestamp() throws Exception { Get get = new Get(toBytes(key)); get.addFamily(family.getBytes()); get.setTimeStamp(t2); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(3, r.rawCells().length); @@ -1013,6 +1048,7 @@ public void testGetOptimizeWithoutQualifierTimestampAndMaxVersion1() throws Exce get.addFamily(family.getBytes()); get.setTimeStamp(timestamps[2]); get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r = hTable.get(get); Assert.assertEquals(4, r.rawCells().length); @@ -1036,6 +1072,7 @@ public void testGetOptimizeWithoutQualifierTimestampAndMaxVersion1() throws Exce get.addFamily(family.getBytes()); get.setTimeStamp(timestamps[4]); get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); Result r2 = hTable.get(get); Assert.assertEquals(4, r2.rawCells().length); @@ -1043,4 +1080,45 @@ public void testGetOptimizeWithoutQualifierTimestampAndMaxVersion1() throws Exce Assert.assertEquals(timestamps[4], r2.rawCells()[i].getTimestamp()); } } + + @Test + public void testGetOptimizeWithGlobalSetting() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + c.set(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE_GLOBAL, "true"); + hTable = new OHTable(c, "test_get_optimize"); + + String family = "family_max_version_1"; + String key = "global_setting_key"; + String col = "col"; + + long baseTime = System.currentTimeMillis(); + for (int i = 1; i <= 5; i++) { + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col.getBytes(), baseTime + i, toBytes("value_" + i)); + hTable.put(put); + } + + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col.getBytes()); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "false".getBytes()); + Result r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals("value_5", Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + + + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col.getBytes()); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); + Result r2 = hTable.get(get); + Assert.assertEquals(1, r2.rawCells().length); + Assert.assertEquals("value_5", Bytes.toString(r2.rawCells()[0].getValueArray(), r2.rawCells()[0].getValueOffset(), r2.rawCells()[0].getValueLength())); + + + get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col.getBytes()); + Result r3 = hTable.get(get); + Assert.assertEquals(1, r3.rawCells().length); + Assert.assertEquals("value_5", Bytes.toString(r3.rawCells()[0].getValueArray(), r3.rawCells()[0].getValueOffset(), r3.rawCells()[0].getValueLength())); + } } From 3e750c15f3db2de1ec0ee7712cd70cb6c59e15d5 Mon Sep 17 00:00:00 2001 From: maochongxin Date: Fri, 12 Dec 2025 11:24:10 +0800 Subject: [PATCH 15/15] add batch get case --- .../com/alipay/oceanbase/hbase/OHTable.java | 10 +- .../hbase/OHTableGetOptimizeTest.java | 115 ++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index 81fe87d7..9702d727 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -2108,8 +2108,16 @@ private ObTableQuery buildObTableQuery(final Get get, Collection columnQ obTableQuery.setScanRangeColumns("K", "Q", "T"); byte[] hotOnly = get.getAttribute(HBASE_HTABLE_QUERY_HOT_ONLY); obTableQuery.setHotOnly(hotOnly != null && Arrays.equals(hotOnly, "true".getBytes())); + boolean hotKeyGetOptimizeEnableBool = false; byte[] hotKeyGetOptimizeEnable = get.getAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE); - obTableQuery.setGetOptimized(hotKeyGetOptimizeEnable != null && Arrays.equals(hotKeyGetOptimizeEnable, "true".getBytes())); + if (hotKeyGetOptimizeEnable == null) { + boolean hotKeyGetOptimizeEnableGlobal = configuration.getBoolean(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE_GLOBAL, false); + hotKeyGetOptimizeEnableBool = hotKeyGetOptimizeEnableGlobal; + } else { + hotKeyGetOptimizeEnableBool = Boolean.parseBoolean(Bytes.toString(hotKeyGetOptimizeEnable)); + } + + obTableQuery.setGetOptimized(hotKeyGetOptimizeEnableBool); return obTableQuery; } diff --git a/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java b/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java index 69ef4a03..374c1c28 100644 --- a/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java +++ b/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java @@ -104,6 +104,14 @@ public void testGetOptimizeWithMaxVersion1() throws Exception { Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + get = new Get(toBytes(key1)); + get.addColumn(family1.getBytes(), column1.getBytes()); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "false".getBytes()); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + // Test min(1, 10) = 1 get = new Get(toBytes(key1)); get.addColumn(family1.getBytes(), column1.getBytes()); @@ -113,6 +121,16 @@ public void testGetOptimizeWithMaxVersion1() throws Exception { Assert.assertEquals(1, r.rawCells().length); Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + get = new Get(toBytes(key1)); + get.addColumn(family1.getBytes(), column1.getBytes()); + get.setMaxVersions(10); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "false".getBytes()); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + + + // Get with setMaxVersions(1) put = new Put(toBytes(key2)); put.addColumn(family2.getBytes(), column1.getBytes(), t1, toBytes(value1)); @@ -136,6 +154,16 @@ public void testGetOptimizeWithMaxVersion1() throws Exception { Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + get = new Get(toBytes(key2)); + get.addColumn(family2.getBytes(), column1.getBytes()); + get.setMaxVersions(1); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "false".getBytes()); + r = hTable.get(get); + Assert.assertEquals(1, r.rawCells().length); + Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(t3, r.rawCells()[0].getTimestamp()); + + // Verify multiple versions exist when requesting them get = new Get(toBytes(key2)); get.addColumn(family2.getBytes(), column1.getBytes()); @@ -146,6 +174,16 @@ public void testGetOptimizeWithMaxVersion1() throws Exception { Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); Assert.assertEquals(value2, Bytes.toString(r.rawCells()[1].getValueArray(), r.rawCells()[1].getValueOffset(), r.rawCells()[1].getValueLength())); Assert.assertEquals(value1, Bytes.toString(r.rawCells()[2].getValueArray(), r.rawCells()[2].getValueOffset(), r.rawCells()[2].getValueLength())); + + get = new Get(toBytes(key2)); + get.addColumn(family2.getBytes(), column1.getBytes()); + get.setMaxVersions(10); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "false".getBytes()); + r = hTable.get(get); + Assert.assertTrue(r.rawCells().length >= 3); + Assert.assertEquals(value3, Bytes.toString(r.rawCells()[0].getValueArray(), r.rawCells()[0].getValueOffset(), r.rawCells()[0].getValueLength())); + Assert.assertEquals(value2, Bytes.toString(r.rawCells()[1].getValueArray(), r.rawCells()[1].getValueOffset(), r.rawCells()[1].getValueLength())); + Assert.assertEquals(value1, Bytes.toString(r.rawCells()[2].getValueArray(), r.rawCells()[2].getValueOffset(), r.rawCells()[2].getValueLength())); } /** @@ -1121,4 +1159,81 @@ public void testGetOptimizeWithGlobalSetting() throws Exception { Assert.assertEquals(1, r3.rawCells().length); Assert.assertEquals("value_5", Bytes.toString(r3.rawCells()[0].getValueArray(), r3.rawCells()[0].getValueOffset(), r3.rawCells()[0].getValueLength())); } + + + // test batch get single cf with global setting + @Test + public void testGetOptimizeBatchGetSingleCfWithGlobalSetting() throws Exception { + Configuration c = ObHTableTestUtil.newConfiguration(); + c.set("rs.list.acquire.read.timeout", "10000"); + c.set(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE_GLOBAL, "true"); + hTable = new OHTable(c, "test_get_optimize"); + + String family = "family_max_version_1"; + String col = "col"; + + // Use different keys for batch get test + List keys = Arrays.asList("batch_key_01", "batch_key_02", "batch_key_03", "batch_key_04", "batch_key_05"); + long baseTime = System.currentTimeMillis(); + + // Put multiple versions for each key + for (String key : keys) { + for (int i = 1; i <= 3; i++) { + Put put = new Put(toBytes(key)); + put.addColumn(family.getBytes(), col.getBytes(), baseTime + i, toBytes(key + "_v" + i)); + hTable.put(put); + } + } + + List gets = new ArrayList<>(); + for (String key : keys) { + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col.getBytes()); + gets.add(get); + } + + // Test with statement-level setting to false (should override global setting) + for (Get get : gets) { + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "false".getBytes()); + } + + Result[] results = hTable.get(gets); + Assert.assertEquals(5, results.length); + for (int i = 0; i < results.length; i++) { + Assert.assertEquals(1, results[i].rawCells().length); + // Should return latest version (v3) when optimize is disabled + Assert.assertEquals(keys.get(i) + "_v3", Bytes.toString(results[i].rawCells()[0].getValueArray(), results[i].rawCells()[0].getValueOffset(), results[i].rawCells()[0].getValueLength())); + } + + // Test with statement-level setting to true (should override global setting) + for (Get get : gets) { + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); + } + + results = hTable.get(gets); + Assert.assertEquals(5, results.length); + for (int i = 0; i < results.length; i++) { + Assert.assertEquals(1, results[i].rawCells().length); + // Should return latest version (v3) when optimize is enabled + Assert.assertEquals(keys.get(i) + "_v3", Bytes.toString(results[i].rawCells()[0].getValueArray(), results[i].rawCells()[0].getValueOffset(), results[i].rawCells()[0].getValueLength())); + } + + // Test without statement-level setting (should use global setting which is true) + gets = new ArrayList<>(); + for (String key : keys) { + Get get = new Get(toBytes(key)); + get.addColumn(family.getBytes(), col.getBytes()); + // Don't set statement-level attribute, should use global setting + gets.add(get); + } + + results = hTable.get(gets); + Assert.assertEquals(5, results.length); + for (int i = 0; i < results.length; i++) { + Assert.assertEquals(1, results[i].rawCells().length); + // Should return latest version (v3) using global setting + Assert.assertEquals(keys.get(i) + "_v3", Bytes.toString(results[i].rawCells()[0].getValueArray(), results[i].rawCells()[0].getValueOffset(), results[i].rawCells()[0].getValueLength())); + } + } + }