diff --git a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java index cdff002f..8589f61f 100644 --- a/src/main/java/com/alipay/oceanbase/hbase/OHTable.java +++ b/src/main/java/com/alipay/oceanbase/hbase/OHTable.java @@ -21,12 +21,15 @@ 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; 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; @@ -63,13 +66,13 @@ 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; 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; @@ -78,7 +81,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 +88,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 { @@ -152,36 +155,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 */ @@ -198,6 +171,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 +204,12 @@ 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())); + } else { + this.metrics = null; + } finishSetUp(); } @@ -281,6 +262,12 @@ 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())); + } else { + this.metrics = null; + } finishSetUp(); } @@ -310,6 +297,7 @@ public OHTable(final byte[] tableName, final ObTableClient obTableClient, this.executePool = executePool; this.obTableClient = obTableClient; this.configuration = new Configuration(); + this.metrics = null; finishSetUp(); } @@ -342,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( @@ -352,6 +337,12 @@ 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())); + } else { + this.metrics = null; + } finishSetUp(); } @@ -386,15 +377,18 @@ 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)); 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())); + } else { + this.metrics = null; + } finishSetUp(); } @@ -455,10 +449,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, @@ -491,6 +481,45 @@ public static OHConnectionConfiguration setUserDefinedNamespace(String tableName return ohConnectionConf; } + private abstract class OperationExecuteCallback { + private final OHOperationType opType; + private final long batchSize; + OperationExecuteCallback(OHOperationType opType, long batchSize) { + this.opType = opType; + this.batchSize = batchSize; + } + abstract T execute() throws IOException; + + public OHOperationType getOpType() { + return this.opType; + } + + public long getBatchSize() { + return this.batchSize; + } + } + + private T execute(OperationExecuteCallback callback) throws IOException { + if (this.metrics != null) { + long startTimeMs = System.currentTimeMillis(); + MetricsImporter importer = new MetricsImporter(); + importer.setBatchSize(callback.getBatchSize()); + try { + return callback.execute(); + } catch (Exception e) { + // do not deal with any exception, just record + importer.setIsFailedOp(true); // set as failed op + throw 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,28 +557,46 @@ 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(); + OHOperationType opType = OHOperationType.EXISTS; + return execute(new OperationExecuteCallback(opType, 1 /* batchSize */) { + @Override + Boolean execute() throws IOException { + Get newGet = new Get(get); + newGet.setCheckExistenceOnly(true); + 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() /* 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 + for (Get get : gets) { + Get newGet = new Get(get); + newGet.setCheckExistenceOnly(true); + 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); // still use list type even executing gets one by one in loop + } + } + for (int i = 0; i < results.length; ++i) { + ret[i] = !results[i].isEmpty(); + } + return ret; + } + }); } @Override @@ -617,6 +664,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 +753,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() /* batchSize */) { + @Override + public Void execute() throws IOException { + innerBatchImpl(actions, results, opType); + return null; // return null for the return type Void, primitive type like void cannot be template type + } + }); + } + + private void innerBatchImpl(final List actions, final Object[] results, final OHOperationType opType) throws IOException { if (actions == null || actions.isEmpty()) { return; } @@ -722,9 +781,9 @@ public void batch(final List actions, final Object[] results) thr } 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) { @@ -739,6 +798,7 @@ public void batch(final List actions, final Object[] results) thr String realTableName = getTargetTableName(actions); BatchOperation batch = buildBatchOperation(realTableName, actions, tableNameString.equals(realTableName), resultMapSingleOp); + batch.setHbaseOpType(opType); BatchOperationResult tmpResults; try { tmpResults = batch.execute(); @@ -848,17 +908,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() /* batchSize */) { + @Override + public Void execute() throws IOException { + try { + innerBatchImpl(actions, results, opType); + 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++) { + 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 +1011,16 @@ private void processColumnFilters(NavigableSet columnFilters, @Override public Result get(final Get get) throws IOException { + OHOperationType opType = OHOperationType.GET; + return execute(new OperationExecuteCallback(opType, 1 /* batchSize */) { + @Override + Result execute() throws IOException { + return innerGetImpl(get, opType); + } + }); + } + + 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()); @@ -954,7 +1031,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 +1049,14 @@ public Result call() throws IOException { processColumnFilters(columnFilters, get.getFamilyMap()); obTableQuery = buildObTableQuery(get, columnFilters); ObTableQueryAsyncRequest request = buildObTableQueryAsyncRequest(obTableQuery, - getTargetTableName(tableNameString)); + getTargetTableName(tableNameString), isWeakRead(get), opType); 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(); @@ -995,16 +1072,16 @@ public Result call() throws IOException { obTableQuery = buildObTableQuery(get, entry.getValue()); ObTableQueryRequest request = buildObTableQueryRequest(obTableQuery, getTargetTableName(tableNameString, Bytes.toString(family), - configuration)); + configuration), isWeakRead(get), opType); ObTableClientQueryStreamResult clientQueryStreamResult = (ObTableClientQueryStreamResult) obTableClient - .execute(request); + .execute(request); getMaxRowFromResult(clientQueryStreamResult, keyValueList, false, - family); + family); } } } catch (Exception 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()); @@ -1019,97 +1096,110 @@ 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() /* batchSize */) { + @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), opType); // still use list type even executing gets one by one in loop + } + } + return results; } - } - return results; + }); } @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 /* batchSize */) { + @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), + isWeakRead(scan), 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), + isWeakRead(scan), 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) { + throw new IOException("scan table:" + tableNameString + " family " + + Bytes.toString(family) + " error.", e); } - } - } catch (Exception 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 { @@ -1147,7 +1237,7 @@ public List call() throws IOException { obTableQuery = buildObTableQuery(filter, scan); request = buildObTableQueryAsyncRequest(obTableQuery, - getTargetTableName(tableNameString)); + getTargetTableName(tableNameString), isWeakRead(scan), OHOperationType.SCAN); request.setNeedTabletId(false); request.setAllowDistributeScan(false); String phyTableName = obTableClient.getPhyTableNameFromTableGroup( @@ -1159,7 +1249,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; @@ -1184,7 +1274,7 @@ public List call() throws IOException { String targetTableName = getTargetTableName(tableNameString, Bytes.toString(family), configuration); - request = buildObTableQueryAsyncRequest(obTableQuery, targetTableName); + request = buildObTableQueryAsyncRequest(obTableQuery, targetTableName, isWeakRead(scan), OHOperationType.SCAN); request.setNeedTabletId(false); request.setAllowDistributeScan(false); List partitions = obTableClient @@ -1195,7 +1285,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; @@ -1228,38 +1318,37 @@ 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 /* batchSize */) { + @Override + public Void execute() throws IOException { + doPut(Collections.singletonList(put), opType); + return null; // return null for the return type Void, primitive type like void cannot be template type + } + }); } @Override public void put(List puts) throws IOException { - doPut(puts); + OHOperationType opType = OHOperationType.PUT_LIST; + execute(new OperationExecuteCallback(opType, puts.size() /* batchSize */) { + @Override + public Void execute() throws IOException { + doPut(puts, opType); + return null; // return null for the return type Void, primitive type like void cannot be template type + } + }); } - private void doPut(List puts) throws IOException { - int n = 0; + private void doPut(List puts, OHOperationType opType) throws IOException { 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(); - } else { - flushCommits(); - } - } } - if (autoFlush || currentWriteBufferSize.get() > writeBufferSize) { - if (OHBaseFuncUtils.isHBasePutPefSupport(obTableClient)) { - flushCommitsV2(); - } else { - flushCommits(); - } + if (OHBaseFuncUtils.isHBasePutPefSupport(obTableClient)) { + flushCommitsV2(puts, opType); + } else { + flushCommits(puts, opType); } } @@ -1333,12 +1422,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) { - 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 @@ -1348,12 +1432,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) { throw e; } @@ -1361,15 +1443,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 /* batchSize */) { + @Override + public Void execute() throws IOException { + checkFamilyViolation(delete.getFamilyCellMap().keySet(), false); + innerDelete(Collections.singletonList(delete), opType); + return null; // return null for the return type Void, primitive type like void cannot be template type + } + }); } @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() /* batchSize */) { + @Override + public Void execute() throws IOException { + innerDelete(deletes, opType); + return null; // return null for the return type Void, primitive type like void cannot be template type + } + }); } /** @@ -1394,12 +1488,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) { - 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 @@ -1413,12 +1503,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) { - 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 @@ -1434,35 +1519,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() /* batchSize */) { + @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.toCamelCase() + " 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 @@ -1479,48 +1575,54 @@ 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 /* batchSize */) { + @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) { + throw new IOException("append table " + tableNameString + " error.", e); + } } - return Result.create(keyValues); - } catch (Exception e) { - throw new IOException("append table " + tableNameString + " error.", e); - } + }); } /** @@ -1532,45 +1634,51 @@ 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 /* batchSize */) { + @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) { + throw new IOException("increment table " + tableNameString + " error.", e); + } } - return Result.create(keyValues); - } catch (Exception e) { - throw new IOException("increment table " + tableNameString + " error.", e); - } + }); } /** @@ -1585,36 +1693,42 @@ 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 /* batchSize */) { + @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) { + throw new IOException("increment table " + tableNameString + " error.", e); + } } - return Bytes.toLong((byte[]) queryResult.getPropertiesRows().get(0).get(3).getValue()); - } catch (Exception e) { - throw new IOException("increment table " + tableNameString + " error.", e); - } + }); } @Override @@ -1623,92 +1737,34 @@ public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, lo return incrementColumnValue(row, family, qualifier, amount); } - public void flushCommits() 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()]; - batch(writeBuffer, results); - 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) { - if (e instanceof IOException) { - throw (IOException) e; - } else { - throw new IOException(tableNameString + " table occurred unexpected error.", 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); + Object[] results = new Object[puts.size()]; + innerBatchImpl(puts, results, opType); + } catch (Exception 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); } } } - public void flushCommitsV2() 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); - try { - ObHbaseResult result = (ObHbaseResult) obTableClient.execute(request); - } catch (Exception e) { - throw new IOException(tableNameString + " table occurred unexpected error.", e); - } - } catch (Exception e) { - if (e instanceof IOException) { - throw (IOException) e; - } else { - throw new IOException(tableNameString + " table occurred unexpected error.", e); - } - } - } finally { - if (clearBufferOnFail) { - writeBuffer.clear(); - currentWriteBufferSize.set(0); + ObHbaseRequest request = buildHbaseRequest(puts, opType); + ObHbaseResult result = (ObHbaseResult) obTableClient.execute(request); + } catch (Exception 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); } } } @@ -1718,6 +1774,9 @@ public void close() throws IOException { if (cleanupPoolOnClose) { executePool.shutdown(); } + if (metrics != null) { + metrics.stop(); + } } @Override @@ -2006,6 +2065,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; } @@ -2025,9 +2098,52 @@ 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); + 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; } + /** + * 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 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; + } + String consistencyStr; + if (consistency == null) { + // fall back to global configuration + consistencyStr = configuration.get(HBASE_HTABLE_READ_CONSISTENCY); + if (consistencyStr == null) { + return false; + } + } else { + consistencyStr = Bytes.toString(consistency); + } + return "weak".equalsIgnoreCase(consistencyStr); + } + public static ObTableBatchOperation buildObTableBatchOperation(List rowList, List qualifiers) { ObTableBatchOperation batch = new ObTableBatchOperation(); @@ -2250,10 +2366,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"); } @@ -2288,6 +2408,10 @@ 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 any Get is not weak read, set allGetIsWeakRead to false + if (!isWeakRead(get)) { + allGetIsWeakRead = false; + } } catch (Exception e) { throw new IOException(e); } @@ -2310,6 +2434,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) { @@ -2359,13 +2484,17 @@ 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)); return batch; } - private ObHbaseRequest buildHbaseRequest(List actions) + private ObHbaseRequest buildHbaseRequest(List actions, OHOperationType hbaseOpType) throws FeatureNotSupportedException, IllegalArgumentException, IOException { @@ -2421,6 +2550,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; @@ -2461,18 +2591,26 @@ public static ObTableOperation buildObTableOperation(Cell kv, } private ObTableQueryRequest buildObTableQueryRequest(ObTableQuery obTableQuery, - String targetTableName) { + String targetTableName, + Boolean isWeakRead, + OHOperationType opType) { ObTableQueryRequest request = new ObTableQueryRequest(); request.setEntityType(ObTableEntityType.HKV); request.setTableQuery(obTableQuery); request.setTableName(targetTableName); + if (isWeakRead) { + request.setConsistencyLevel(ObReadConsistency.WEAK); + } request.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); request.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); + request.setHbaseOpType(opType); return request; } private ObTableQueryAsyncRequest buildObTableQueryAsyncRequest(ObTableQuery obTableQuery, - String targetTableName) { + String targetTableName, + Boolean isWeakRead, + OHOperationType opType) { ObTableQueryRequest request = new ObTableQueryRequest(); request.setEntityType(ObTableEntityType.HKV); request.setTableQuery(obTableQuery); @@ -2483,12 +2621,17 @@ private ObTableQueryAsyncRequest buildObTableQueryAsyncRequest(ObTableQuery obTa asyncRequest.setObTableQueryRequest(request); asyncRequest.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); asyncRequest.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); + if (isWeakRead) { + asyncRequest.setConsistencyLevel(ObReadConsistency.WEAK); + } + 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); @@ -2499,6 +2642,7 @@ private ObTableQueryAndMutateRequest buildObTableQueryAndMutateRequest(ObTableQu request.setReturningAffectedEntity(true); request.setServerCanRetry(OHBaseFuncUtils.serverCanRetry(obTableClient)); request.setNeedTabletId(OHBaseFuncUtils.needTabletId(obTableClient)); + request.setHbaseOpType(opType); return request; } @@ -2676,13 +2820,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) { - 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 @@ -2690,25 +2829,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) { - 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) { - 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() { @@ -2717,4 +2846,10 @@ 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..660c491e 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.metrics.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/constants/OHConstants.java b/src/main/java/com/alipay/oceanbase/hbase/constants/OHConstants.java index deb94f75..a0350c61 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,32 @@ 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 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. + */ + 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/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/metrics/MetricsExporter.java b/src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsExporter.java new file mode 100644 index 00000000..4898c029 --- /dev/null +++ b/src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsExporter.java @@ -0,0 +1,150 @@ +package com.alipay.oceanbase.hbase.metrics; + +import com.codahale.metrics.Snapshot; +import com.codahale.metrics.Timer; + +public class MetricsExporter { + 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; // ms + private double failRate; + private double averageSingleOpCount; + + 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; + } + + 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; + } + + public long getCount() { + return totalOpCount; + } + + public double getAverageOps() { + return averageOps; + } + + public double getOneMinuteAverageOps() { + return oneMinuteAverageOps; + } + + public double getFiveMinuteAverageOps() { + return fiveMinuteAverageOps; + } + + public double getFifteenMinuteAverageOps() { + return fifteenMinuteAverageOps; + } + + public double getFailRate() { + return failRate; + } + + public long getFailCount() { + return failCount; + } + + public long getTotalRuntime() { + return totalRuntime; + } + + public double getAverageSingleOpCount() { + return averageSingleOpCount; + } + + public double getAverageLatency() { + return averageLatency; + } + + public long getMaxLatency() { + return maxLatency; + } + + public long getMinLatency() { + return minLatency; + } + + public double getMedian() { + return medianLatency; + } + + public double get75thPercentile() { + return P75thPercentile; + } + + public double get95thPercentile() { + return P95thPercentile; + } + + public double get98thPercentile() { + return P98thPercentile; + } + + public double get99thPercentile() { + return P99thPercentile; + } + + public double get999thPercentile() { + return P999thPercentile; + } + + public static MetricsExporter getInstanceOf(double averageSingleOpCount, + double failRate, + long failCount, + long totalRuntime, + Timer latencyHistogram) { + MetricsExporter exporter = new MetricsExporter(latencyHistogram); + exporter.setAverageSingleOpCount(averageSingleOpCount); + exporter.setFailRate(failRate); + exporter.setFailCount(failCount); + exporter.setTotalRuntime(totalRuntime); + return exporter; + } +} diff --git a/src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsImporter.java b/src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsImporter.java new file mode 100644 index 00000000..4230bc0a --- /dev/null +++ b/src/main/java/com/alipay/oceanbase/hbase/metrics/MetricsImporter.java @@ -0,0 +1,37 @@ +package com.alipay.oceanbase.hbase.metrics; + +public class MetricsImporter { + private boolean isFailedOp; + private long duration; + private long batchSize; + + public MetricsImporter() { + this.isFailedOp = false; + this.duration = 0; + this.batchSize = 0; + } + + public void setIsFailedOp(boolean isFailedOp) { + this.isFailedOp = isFailedOp; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public void setBatchSize(long batchSize) { + this.batchSize = batchSize; + } + + public boolean isFailedOp() { + return isFailedOp; + } + + public long getDuration() { + return duration; + } + + public long getBatchSize() { + return batchSize; + } +} diff --git a/src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetrics.java b/src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetrics.java new file mode 100644 index 00000000..c3afc7d6 --- /dev/null +++ b/src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetrics.java @@ -0,0 +1,89 @@ +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; +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 - 1]; + // OHOperationType(0) is INVALID, skip it + for (int i = 1; i <= trackers.length; ++i) { + OHOperationType opType = OHOperationType.valueOf(i); + trackers[i - 1] = new OHMetricsTracker(this.registry, + metricsName, + opType); + } + this.reporter = JmxReporter.forRegistry(this.registry) + .inDomain("com.alipay.oceanbase.hbase.metrics") + .build(); + this.reporter.start(); + 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() { + 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() - 1]; + } + + 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(); + } + + 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/metrics/OHMetricsTracker.java b/src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetricsTracker.java new file mode 100644 index 00000000..daf92818 --- /dev/null +++ b/src/main/java/com/alipay/oceanbase/hbase/metrics/OHMetricsTracker.java @@ -0,0 +1,57 @@ +package com.alipay.oceanbase.hbase.metrics; + +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(metricsName, typeName, "latencyHistogram")); + this.failedOpCounter = registry.meter( + name(metricsName, typeName, "failedOpCounter")); + this.totalSingleOpCount = registry.counter( + name(metricsName, typeName, "totalSingleOpCount")); + this.totalRuntime = registry.counter( + name(metricsName, typeName, "totalRuntime")); + } + + 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.getBatchSize()); + totalRuntime.inc(importer.getDuration()); + } + + 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 + 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/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java b/src/main/java/com/alipay/oceanbase/hbase/result/ClientStreamScanner.java index c139d8b6..49598a49 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.metrics.MetricsImporter; import com.alipay.oceanbase.hbase.util.OHBaseFuncUtils; +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; +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; @@ -29,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; @@ -55,26 +57,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(); @@ -133,6 +141,13 @@ public Result next() throws IOException { } catch (Exception 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.setBatchSize(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 61b4ef65..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) { @@ -122,4 +126,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/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..56feb224 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,8 @@ 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; @@ -118,6 +120,12 @@ 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())); + } obTableClient.init(); OB_TABLE_CLIENT_INSTANCE.put(obTableClientKey, obTableClient); } 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..098c367e --- /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.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; +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 new file mode 100644 index 00000000..2d17522d --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/hbase/OHMetricsTest.java @@ -0,0 +1,911 @@ +package com.alipay.oceanbase.hbase; + +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; +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; +import static org.junit.Assert.fail; + +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); + + 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.PUT); + failureMetricsChecker(OHOperationType.PUT, newExporter, failedExporter); + } + + @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); + + // 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.GET); + failureMetricsChecker(OHOperationType.GET, newExporter, failedExporter); + } + + @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); + + // 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.GET_LIST); + failureMetricsChecker(OHOperationType.GET_LIST, newExporter, failedExporter); + } + + @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); + + 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); + metricsChecker(OHOperationType.PUT_LIST, oldExporter, newExporter); + + // test failed op metrics + 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); + fail(); + } catch (Exception e) { + System.out.println(e.getMessage()); + Assert.assertTrue(e.getMessage().contains("No columns")); + } + Thread.sleep(11000); // sleep over 10 second + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); + failureMetricsChecker(OHOperationType.PUT_LIST, newExporter, failedExporter); + } + + @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); + + // 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); + metricsChecker(OHOperationType.DELETE, oldExporter, newExporter); + + // test failed op metrics + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.DELETE); + failureMetricsChecker(OHOperationType.DELETE, newExporter, failedExporter); + } + + @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); + + // 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); + 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) { + System.out.println("DELETE_LIST error: " + e.getMessage()); + Assert.assertTrue(e.getMessage().contains("not_exist_table")); + } + Thread.sleep(11000); // sleep over 10 second + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.DELETE_LIST); + failureMetricsChecker(OHOperationType.DELETE_LIST, newExporter, failedExporter); + } + + @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); + + // 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); + metricsChecker(OHOperationType.EXISTS, oldExporter, newExporter); + + Assert.assertEquals(0L, newExporter.getFailCount()); + try { + Table notExistTable = connection.getTable(TableName.valueOf("not_exist_table")); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.EXISTS); + failureMetricsChecker(OHOperationType.EXISTS, newExporter, failedExporter); + } + + @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); + + // 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.EXISTS_LIST); + failureMetricsChecker(OHOperationType.EXISTS_LIST, newExporter, failedExporter); + } + + @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); + + // 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.SCAN); + failureMetricsChecker(OHOperationType.SCAN, newExporter, failedExporter); + } + + @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); + + 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.BATCH); + failureMetricsChecker(OHOperationType.BATCH, newExporter, failedExporter); + } + + @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); + + 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); + metricsChecker(OHOperationType.BATCH_CALLBACK, oldExporter, newExporter); + + 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 + } + }); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.BATCH_CALLBACK); + failureMetricsChecker(OHOperationType.BATCH_CALLBACK, newExporter, failedExporter); + } + + @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); + + // 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); + metricsChecker(OHOperationType.CHECK_AND_PUT, oldExporter, newExporter); + + 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_PUT); + failureMetricsChecker(OHOperationType.CHECK_AND_PUT, newExporter, failedExporter); + } + + @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); + + // 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_DELETE); + failureMetricsChecker(OHOperationType.CHECK_AND_DELETE, newExporter, failedExporter); + } + + @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); + + // 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.CHECK_AND_MUTATE); + failureMetricsChecker(OHOperationType.CHECK_AND_MUTATE, newExporter, failedExporter); + } + + @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); + + Append append = new Append(Bytes.toBytes(row1)); + 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.APPEND); + failureMetricsChecker(OHOperationType.APPEND, newExporter, failedExporter); + } + + @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); + + 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.INCREMENT); + failureMetricsChecker(OHOperationType.INCREMENT, newExporter, failedExporter); + } + + @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); + + 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); + 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 + MetricsExporter failedExporter = metrics.acquireMetrics(OHOperationType.INCREMENT_COLUMN_VALUE); + failureMetricsChecker(OHOperationType.INCREMENT_COLUMN_VALUE, newExporter, failedExporter); + } + + @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); + List puts = new ArrayList<>(); + hTable.put(puts); + Thread.sleep(11000); // sleep over 10 second + MetricsExporter newExporter = metrics.acquireMetrics(OHOperationType.PUT_LIST); + // 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(oldExporter.getAverageSingleOpCount(), newExporter.getAverageSingleOpCount(), 0.001); + + // test batch + oldExporter = metrics.acquireMetrics(OHOperationType.BATCH); + 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(oldExporter.getAverageSingleOpCount(), 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); + MetricsExporter oldExporter = metrics.acquireMetrics(OHOperationType.PUT); + long oldCount = oldExporter.getCount(); + + 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 newExporter = metrics.acquireMetrics(OHOperationType.PUT); + long realCount = newExporter.getCount() - oldCount; + long expectedCount = threadCount * operationsPerThread; + System.out.println("real count: " + realCount); + System.out.println("expected count: " + 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/OHTableGetOptimizeTest.java b/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java new file mode 100644 index 00000000..374c1c28 --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/hbase/OHTableGetOptimizeTest.java @@ -0,0 +1,1239 @@ +/*- + * #%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 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; + +/** + * 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()); + 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())); + 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()); + 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())); + + 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)); + 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); + 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())); + 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()); + 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())); + 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())); + } + + /** + * 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()); + 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())); + 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()); + 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())); + } + + /** + * 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); + } + + 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++) { + 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); + 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())); + + // Get with setMaxVersions(3) + 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())); + 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()); + 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())); + } + + /** + * 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); + 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); + // 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); + 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())); + } + + /** + * 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); + 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())); + 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); + 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())); + 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); + 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())); + 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); + 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())); + 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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); + 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]); + 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())); + 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); + 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())); + 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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); + 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()); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".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()[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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); + 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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".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()); + 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); + 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())); + + // Test: min(1, 5) = 1 + 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())); + + // Test: min(1, 10) = 1 + 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())); + } + + /** + * 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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".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("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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".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()[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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".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()); + 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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".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()[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); + get.setAttribute(HBASE_HTABLE_HOTKEY_GET_OPTIMIZE_ENABLE, "true".getBytes()); + 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()); + } + } + + @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())); + } + + + // 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())); + } + } + +} 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..301880f7 --- /dev/null +++ b/src/test/java/com/alipay/oceanbase/hbase/ObTableWeakReadTest.java @@ -0,0 +1,3277 @@ +/*- + * #%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 = ""; + 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_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 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; + 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 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; + + 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); + // 在 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); + } + } + + /** + * 获取租户连接 + */ + 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); + } + + /** + * 获取代理连接 + */ + 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); + } + + @org.junit.AfterClass + public static void afterClass() throws Exception { + if (clear) { + dropTable(staticTenantConnection); + } + } + + @Before + public void setup() throws Exception { + tenantConnection = staticTenantConnection; + sysConnection = staticSysConnection; + proxyConnection = staticProxyConnection; + 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)); + } + } + + 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上进行读取 + */ + @Test + 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)); + // 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 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()))); + + // 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()); + } + + /* + * 测试场景:全局配置为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()); + } +} 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/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java b/src/test/java/com/alipay/oceanbase/hbase/util/JMXMetricsTestHelper.java new file mode 100644 index 00000000..0244c414 --- /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.hbase.metrics.OHMetrics; +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.alipay.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: test_multi_cf.test.PUT.latencyHistogram + String name = String.format("%s.%s.%s", + metricsName, + opType.name(), + attributeName); + + 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); + } + } +} + 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;