From 5d71b60af1c006a416ba9510c00202ce476d0006 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Mon, 24 Nov 2025 16:37:11 +0530 Subject: [PATCH 01/10] PHOENIX-7727 Eliminate IndexMetadataCache RPCs by leveraging server PTable cache --- .../index/IndexMetaDataCacheClient.java | 15 +- .../apache/phoenix/query/QueryServices.java | 1 + .../phoenix/query/QueryServicesOptions.java | 3 + .../index/PhoenixIndexMetaDataBuilder.java | 142 +++++++++++++++--- 4 files changed, 138 insertions(+), 23 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java index aa03f4a76c9..7125a8bab17 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java @@ -18,6 +18,7 @@ package org.apache.phoenix.index; import static org.apache.phoenix.query.QueryServices.INDEX_MUTATE_BATCH_SIZE_THRESHOLD_ATTRIB; +import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; import static org.apache.phoenix.schema.types.PDataType.TRUE_BYTES; import java.sql.SQLException; @@ -34,13 +35,18 @@ import org.apache.phoenix.join.MaxServerCacheSizeExceededException; import org.apache.phoenix.query.QueryServicesOptions; import org.apache.phoenix.schema.PTable; +import org.apache.phoenix.schema.PTableType; import org.apache.phoenix.util.ByteUtil; import org.apache.phoenix.util.PhoenixRuntime; import org.apache.phoenix.util.ReadOnlyProps; import org.apache.phoenix.util.ScanUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class IndexMetaDataCacheClient { + private static final Logger LOGGER = LoggerFactory.getLogger(IndexMetaDataCacheClient.class); + private final ServerCacheClient serverCache; private PTable cacheUsingTable; @@ -120,8 +126,15 @@ public static ServerCache setMetaDataOnMutations(PhoenixConnection connection, P txState = connection.getMutationState().encodeTransaction(); } boolean hasIndexMetaData = indexMetaDataPtr.getLength() > 0; + ReadOnlyProps props = connection.getQueryServices().getProps(); + boolean useServerMetadata = props.getBoolean(INDEX_USE_SERVER_METADATA_ATTRIB, + QueryServicesOptions.DEFAULT_INDEX_USE_SERVER_METADATA); if (hasIndexMetaData) { - if ( + if (useServerMetadata && table.getType() != PTableType.SYSTEM) { + LOGGER.trace("Using server-side metadata for table {}, not sending IndexMaintainer or UUID", + table.getTableName()); + uuidValue = ByteUtil.EMPTY_BYTE_ARRAY; + } else if ( useIndexMetadataCache(connection, mutations, indexMetaDataPtr.getLength() + txState.length) ) { IndexMetaDataCacheClient client = new IndexMetaDataCacheClient(connection, table); diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServices.java b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServices.java index 66b8fe53792..dc5d042e464 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServices.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServices.java @@ -115,6 +115,7 @@ public interface QueryServices extends SQLCloseable { public static final String IMMUTABLE_ROWS_ATTRIB = "phoenix.mutate.immutableRows"; public static final String INDEX_MUTATE_BATCH_SIZE_THRESHOLD_ATTRIB = "phoenix.index.mutableBatchSizeThreshold"; + public static final String INDEX_USE_SERVER_METADATA_ATTRIB = "phoenix.index.useServerMetadata"; public static final String DROP_METADATA_ATTRIB = "phoenix.schema.dropMetaData"; public static final String GROUPBY_SPILLABLE_ATTRIB = "phoenix.groupby.spillable"; public static final String GROUPBY_SPILL_FILES_ATTRIB = "phoenix.groupby.spillFiles"; diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java index 5e345f4901e..2af299aa2c3 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java @@ -66,6 +66,7 @@ import static org.apache.phoenix.query.QueryServices.INDEX_MUTATE_BATCH_SIZE_THRESHOLD_ATTRIB; import static org.apache.phoenix.query.QueryServices.INDEX_POPULATION_SLEEP_TIME; import static org.apache.phoenix.query.QueryServices.INDEX_REBUILD_TASK_INITIAL_DELAY; +import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; import static org.apache.phoenix.query.QueryServices.IS_NAMESPACE_MAPPING_ENABLED; import static org.apache.phoenix.query.QueryServices.IS_SYSTEM_TABLE_MAPPED_TO_NAMESPACE; import static org.apache.phoenix.query.QueryServices.KEEP_ALIVE_MS_ATTRIB; @@ -202,6 +203,7 @@ public class QueryServicesOptions { public static final int DEFAULT_DISTINCT_VALUE_COMPRESS_THRESHOLD = 1024 * 1024 * 1; // 1 Mb public static final int DEFAULT_AGGREGATE_CHUNK_SIZE_INCREASE = 1024 * 1024 * 1; // 1 Mb public static final int DEFAULT_INDEX_MUTATE_BATCH_SIZE_THRESHOLD = 3; + public static final boolean DEFAULT_INDEX_USE_SERVER_METADATA = true; public static final long DEFAULT_MAX_SPOOL_TO_DISK_BYTES = 1024000000; // Only the first chunked batches are fetched in parallel, so this default // should be on the relatively bigger side of things. Bigger means more @@ -551,6 +553,7 @@ public static QueryServicesOptions withDefaults() { .setIfUnset(IMMUTABLE_ROWS_ATTRIB, DEFAULT_IMMUTABLE_ROWS) .setIfUnset(INDEX_MUTATE_BATCH_SIZE_THRESHOLD_ATTRIB, DEFAULT_INDEX_MUTATE_BATCH_SIZE_THRESHOLD) + .setIfUnset(INDEX_USE_SERVER_METADATA_ATTRIB, DEFAULT_INDEX_USE_SERVER_METADATA) .setIfUnset(MAX_SPOOL_TO_DISK_BYTES_ATTRIB, DEFAULT_MAX_SPOOL_TO_DISK_BYTES) .setIfUnset(DROP_METADATA_ATTRIB, DEFAULT_DROP_METADATA) .setIfUnset(GROUPBY_SPILLABLE_ATTRIB, DEFAULT_GROUPBY_SPILLABLE) diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java index 45f70d3bbb2..dc1b301490f 100644 --- a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java +++ b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java @@ -18,7 +18,9 @@ package org.apache.phoenix.index; import java.io.IOException; +import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.hadoop.hbase.client.Mutation; @@ -32,14 +34,26 @@ import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.exception.SQLExceptionInfo; +import org.apache.phoenix.execute.MutationState; import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr; +import org.apache.phoenix.jdbc.PhoenixConnection; +import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; +import org.apache.phoenix.query.QueryConstants; +import org.apache.phoenix.schema.PTable; import org.apache.phoenix.transaction.PhoenixTransactionContext; import org.apache.phoenix.transaction.TransactionFactory; import org.apache.phoenix.util.ClientUtil; import org.apache.phoenix.util.PhoenixRuntime; +import org.apache.phoenix.util.QueryUtil; import org.apache.phoenix.util.ScanUtil; +import org.apache.phoenix.util.SchemaUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PhoenixIndexMetaDataBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(PhoenixIndexMetaDataBuilder.class); + private final RegionCoprocessorEnvironment env; PhoenixIndexMetaDataBuilder(RegionCoprocessorEnvironment env) { @@ -59,6 +73,17 @@ private static IndexMetaDataCache getIndexMetaDataCache(RegionCoprocessorEnviron if (attributes == null) { return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; } + if ( + !env.getRegion().getTableDescriptor().getTableName().getNameAsString() + .startsWith(PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA + QueryConstants.NAME_SEPARATOR) + && !env.getRegion().getTableDescriptor().getTableName().getNameAsString().startsWith( + PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA + QueryConstants.NAMESPACE_SEPARATOR) + ) { + IndexMetaDataCache cacheFromPTable = getIndexMetaDataCacheFromPTable(env, attributes); + if (cacheFromPTable != null) { + return cacheFromPTable; + } + } byte[] uuid = attributes.get(PhoenixIndexCodec.INDEX_UUID); if (uuid == null) { return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; @@ -77,28 +102,7 @@ private static IndexMetaDataCache getIndexMetaDataCache(RegionCoprocessorEnviron : Bytes.toInt(clientVersionBytes); final PhoenixTransactionContext txnContext = TransactionFactory.getTransactionContext(txState, clientVersion); - return new IndexMetaDataCache() { - - @Override - public void close() throws IOException { - } - - @Override - public List getIndexMaintainers() { - return indexMaintainers; - } - - @Override - public PhoenixTransactionContext getTransactionContext() { - return txnContext; - } - - @Override - public int getClientVersion() { - return clientVersion; - } - - }; + return getIndexMetaDataCache(clientVersion, txnContext, indexMaintainers); } else { byte[] tenantIdBytes = attributes.get(PhoenixRuntime.TENANT_ID_ATTRIB); ImmutableBytesPtr tenantId = @@ -117,4 +121,98 @@ public int getClientVersion() { } } + + /** + * Get IndexMetaDataCache by looking up PTable using table metadata attributes attached to the + * mutation. + * @param env RegionCoprocessorEnvironment. + * @param attributes Mutation attributes. + * @return IndexMetaDataCache or null if table metadata not found in attributes. + */ + private static IndexMetaDataCache getIndexMetaDataCacheFromPTable( + RegionCoprocessorEnvironment env, Map attributes) { + try { + byte[] schemaBytes = + attributes.get(MutationState.MutationMetadataType.SCHEMA_NAME.toString()); + byte[] tableBytes = + attributes.get(MutationState.MutationMetadataType.LOGICAL_TABLE_NAME.toString()); + if (schemaBytes == null || tableBytes == null) { + LOGGER.error("Table metadata for table name and schema name not found in mutation " + + "attributes, falling back to GlobalCache lookup"); + return null; + } + byte[] tenantIdBytes = + attributes.get(MutationState.MutationMetadataType.TENANT_ID.toString()); + byte[] txState = attributes.get(BaseScannerRegionObserverConstants.TX_STATE); + byte[] clientVersionBytes = attributes.get(BaseScannerRegionObserverConstants.CLIENT_VERSION); + + final int clientVersion = clientVersionBytes == null + ? ScanUtil.UNKNOWN_CLIENT_VERSION + : Bytes.toInt(clientVersionBytes); + final PhoenixTransactionContext txnContext = + TransactionFactory.getTransactionContext(txState, clientVersion); + + String fullTableName = SchemaUtil.getTableName(schemaBytes, tableBytes); + Connection conn = QueryUtil.getConnectionOnServer(env.getConfiguration()); + PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class); + + String tenantId = + tenantIdBytes == null || tenantIdBytes.length == 0 ? null : Bytes.toString(tenantIdBytes); + PTable dataTable = pconn.getTable(tenantId, fullTableName); + + final List indexMaintainers = + buildIndexMaintainersFromPTable(dataTable, pconn); + if (indexMaintainers.isEmpty()) { + LOGGER.debug("No active indexes found for table {}", fullTableName); + return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; + } + return getIndexMetaDataCache(clientVersion, txnContext, indexMaintainers); + } catch (Exception e) { + LOGGER.warn("Failed to get PTable from CQSI cache, falling back to GlobalCache lookup", e); + return null; + } + } + + private static IndexMetaDataCache getIndexMetaDataCache(int clientVersion, + PhoenixTransactionContext txnContext, List indexMaintainers) { + return new IndexMetaDataCache() { + @Override + public void close() throws IOException { + } + + @Override + public List getIndexMaintainers() { + return indexMaintainers; + } + + @Override + public PhoenixTransactionContext getTransactionContext() { + return txnContext; + } + + @Override + public int getClientVersion() { + return clientVersion; + } + }; + } + + /** + * Build List of IndexMaintainer for each active index. + * @param dataTable PTable of the data table. + * @param connection PhoenixConnection. + * @return List of IndexMaintainer objects for active indexes. + */ + private static List buildIndexMaintainersFromPTable(PTable dataTable, + PhoenixConnection connection) throws SQLException { + List indexMaintainers = new ArrayList<>(); + List indexes = dataTable.getIndexes(); + for (PTable index : indexes) { + if (IndexMaintainer.sendIndexMaintainer(index)) { + IndexMaintainer maintainer = IndexMaintainer.create(dataTable, index, connection); + indexMaintainers.add(maintainer); + } + } + return indexMaintainers; + } } From ba527333d3eadbc9f8928d2ca884eb85420b1366 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Tue, 25 Nov 2025 11:12:25 +0530 Subject: [PATCH 02/10] conn --- .../index/PhoenixIndexMetaDataBuilder.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java index dc1b301490f..42c13f48bab 100644 --- a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java +++ b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java @@ -153,20 +153,19 @@ private static IndexMetaDataCache getIndexMetaDataCacheFromPTable( TransactionFactory.getTransactionContext(txState, clientVersion); String fullTableName = SchemaUtil.getTableName(schemaBytes, tableBytes); - Connection conn = QueryUtil.getConnectionOnServer(env.getConfiguration()); - PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class); - - String tenantId = - tenantIdBytes == null || tenantIdBytes.length == 0 ? null : Bytes.toString(tenantIdBytes); - PTable dataTable = pconn.getTable(tenantId, fullTableName); - - final List indexMaintainers = - buildIndexMaintainersFromPTable(dataTable, pconn); - if (indexMaintainers.isEmpty()) { - LOGGER.debug("No active indexes found for table {}", fullTableName); - return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; + try (Connection conn = QueryUtil.getConnectionOnServer(env.getConfiguration())) { + PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class); + String tenantId = + tenantIdBytes == null || tenantIdBytes.length == 0 ? null : Bytes.toString(tenantIdBytes); + PTable dataTable = pconn.getTable(tenantId, fullTableName); + final List indexMaintainers = + buildIndexMaintainersFromPTable(dataTable, pconn); + if (indexMaintainers.isEmpty()) { + LOGGER.debug("No active indexes found for table {}", fullTableName); + return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; + } + return getIndexMetaDataCache(clientVersion, txnContext, indexMaintainers); } - return getIndexMetaDataCache(clientVersion, txnContext, indexMaintainers); } catch (Exception e) { LOGGER.warn("Failed to get PTable from CQSI cache, falling back to GlobalCache lookup", e); return null; From 092bb32e485489cf94c929384968d5e15b2b6a08 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Tue, 25 Nov 2025 19:19:48 +0530 Subject: [PATCH 03/10] few changes --- .../index/PhoenixIndexMetaDataBuilder.java | 23 ++++++++++++++----- .../end2end/LocalIndexSplitMergeIT.java | 5 ++-- .../end2end/UCFWithDisabledIndexIT.java | 2 ++ ...FWithDisabledIndexWithDDLValidationIT.java | 3 +++ 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java index 42c13f48bab..c44573f33cd 100644 --- a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java +++ b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java @@ -17,12 +17,15 @@ */ package org.apache.phoenix.index; +import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; + import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Properties; import org.apache.hadoop.hbase.client.Mutation; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress; @@ -39,6 +42,7 @@ import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; import org.apache.phoenix.query.QueryConstants; +import org.apache.phoenix.query.QueryServicesOptions; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.transaction.PhoenixTransactionContext; import org.apache.phoenix.transaction.TransactionFactory; @@ -73,9 +77,12 @@ private static IndexMetaDataCache getIndexMetaDataCache(RegionCoprocessorEnviron if (attributes == null) { return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; } + boolean useServerMetadata = env.getConfiguration().getBoolean(INDEX_USE_SERVER_METADATA_ATTRIB, + QueryServicesOptions.DEFAULT_INDEX_USE_SERVER_METADATA); if ( - !env.getRegion().getTableDescriptor().getTableName().getNameAsString() - .startsWith(PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA + QueryConstants.NAME_SEPARATOR) + useServerMetadata + && !env.getRegion().getTableDescriptor().getTableName().getNameAsString() + .startsWith(PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA + QueryConstants.NAME_SEPARATOR) && !env.getRegion().getTableDescriptor().getTableName().getNameAsString().startsWith( PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA + QueryConstants.NAMESPACE_SEPARATOR) ) { @@ -153,16 +160,20 @@ private static IndexMetaDataCache getIndexMetaDataCacheFromPTable( TransactionFactory.getTransactionContext(txState, clientVersion); String fullTableName = SchemaUtil.getTableName(schemaBytes, tableBytes); - try (Connection conn = QueryUtil.getConnectionOnServer(env.getConfiguration())) { + String tenantId = + tenantIdBytes == null || tenantIdBytes.length == 0 ? null : Bytes.toString(tenantIdBytes); + Properties props = new Properties(); + if (tenantId != null) { + props.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, tenantId); + } + try (Connection conn = QueryUtil.getConnectionOnServer(props, env.getConfiguration())) { PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class); - String tenantId = - tenantIdBytes == null || tenantIdBytes.length == 0 ? null : Bytes.toString(tenantIdBytes); PTable dataTable = pconn.getTable(tenantId, fullTableName); final List indexMaintainers = buildIndexMaintainersFromPTable(dataTable, pconn); if (indexMaintainers.isEmpty()) { LOGGER.debug("No active indexes found for table {}", fullTableName); - return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; + return null; } return getIndexMetaDataCache(clientVersion, txnContext, indexMaintainers); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java index e654fe0c19c..87f09e3adc6 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java @@ -17,6 +17,7 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -76,8 +77,8 @@ private void createBaseTable(String tableName, String splits) throws SQLExceptio Connection conn = getConnectionForLocalIndexTest(); String ddl = "CREATE TABLE " + tableName + " (t_id VARCHAR NOT NULL,\n" + "k1 INTEGER NOT NULL,\n" + "k2 INTEGER NOT NULL,\n" + "k3 INTEGER,\n" + "v1 VARCHAR,\n" - + "CONSTRAINT pk PRIMARY KEY (t_id, k1, k2))\n" - + (splits != null ? (" split on " + splits) : ""); + + "CONSTRAINT pk PRIMARY KEY (t_id, k1, k2)) \"" + INDEX_USE_SERVER_METADATA_ATTRIB + + "\"=false" + " \n" + (splits != null ? (" split on " + splits) : ""); conn.createStatement().execute(ddl); conn.close(); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexIT.java index d061f161105..a1372be5496 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexIT.java @@ -18,6 +18,7 @@ package org.apache.phoenix.end2end; import static org.apache.phoenix.query.QueryServices.DISABLE_VIEW_SUBTREE_VALIDATION; +import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; import com.google.protobuf.RpcCallback; import com.google.protobuf.RpcController; @@ -75,6 +76,7 @@ private static void initCluster() throws Exception { props.put(QueryServices.PHOENIX_METADATA_INVALIDATE_CACHE_ENABLED, Boolean.toString(false)); props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(Long.MAX_VALUE)); props.put(DISABLE_VIEW_SUBTREE_VALIDATION, "true"); + props.put(INDEX_USE_SERVER_METADATA_ATTRIB, Boolean.toString(false)); setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator())); } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexWithDDLValidationIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexWithDDLValidationIT.java index ef2636e2ecc..8c63fd90d81 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexWithDDLValidationIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithDisabledIndexWithDDLValidationIT.java @@ -17,6 +17,8 @@ */ package org.apache.phoenix.end2end; +import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; + import java.util.Map; import org.apache.phoenix.coprocessorclient.BaseScannerRegionObserverConstants; import org.apache.phoenix.query.QueryServices; @@ -43,6 +45,7 @@ private static void initCluster() throws Exception { props.put(QueryServices.LAST_DDL_TIMESTAMP_VALIDATION_ENABLED, Boolean.toString(true)); props.put(QueryServices.PHOENIX_METADATA_INVALIDATE_CACHE_ENABLED, Boolean.toString(true)); props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(Long.MAX_VALUE)); + props.put(INDEX_USE_SERVER_METADATA_ATTRIB, Boolean.toString(false)); setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator())); } From 718a3844afb4e68eb5c8227bda221b9e1f6e8557 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Tue, 25 Nov 2025 20:01:53 +0530 Subject: [PATCH 04/10] compatibility --- .../index/IndexMetaDataCacheClient.java | 5 ++++- .../index/PhoenixIndexMetaDataBuilder.java | 18 +++--------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java index 7125a8bab17..393235b51fb 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java @@ -33,6 +33,7 @@ import org.apache.phoenix.coprocessorclient.MetaDataProtocol; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.join.MaxServerCacheSizeExceededException; +import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.query.QueryServicesOptions; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.schema.PTableType; @@ -128,7 +129,9 @@ public static ServerCache setMetaDataOnMutations(PhoenixConnection connection, P boolean hasIndexMetaData = indexMetaDataPtr.getLength() > 0; ReadOnlyProps props = connection.getQueryServices().getProps(); boolean useServerMetadata = props.getBoolean(INDEX_USE_SERVER_METADATA_ATTRIB, - QueryServicesOptions.DEFAULT_INDEX_USE_SERVER_METADATA); + QueryServicesOptions.DEFAULT_INDEX_USE_SERVER_METADATA) + && props.getBoolean(QueryServices.INDEX_REGION_OBSERVER_ENABLED_ATTRIB, + QueryServicesOptions.DEFAULT_INDEX_REGION_OBSERVER_ENABLED); if (hasIndexMetaData) { if (useServerMetadata && table.getType() != PTableType.SYSTEM) { LOGGER.trace("Using server-side metadata for table {}, not sending IndexMaintainer or UUID", diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java index c44573f33cd..8127cafd324 100644 --- a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java +++ b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java @@ -17,8 +17,6 @@ */ package org.apache.phoenix.index; -import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; - import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; @@ -40,9 +38,6 @@ import org.apache.phoenix.execute.MutationState; import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr; import org.apache.phoenix.jdbc.PhoenixConnection; -import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; -import org.apache.phoenix.query.QueryConstants; -import org.apache.phoenix.query.QueryServicesOptions; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.transaction.PhoenixTransactionContext; import org.apache.phoenix.transaction.TransactionFactory; @@ -77,21 +72,14 @@ private static IndexMetaDataCache getIndexMetaDataCache(RegionCoprocessorEnviron if (attributes == null) { return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; } - boolean useServerMetadata = env.getConfiguration().getBoolean(INDEX_USE_SERVER_METADATA_ATTRIB, - QueryServicesOptions.DEFAULT_INDEX_USE_SERVER_METADATA); - if ( - useServerMetadata - && !env.getRegion().getTableDescriptor().getTableName().getNameAsString() - .startsWith(PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA + QueryConstants.NAME_SEPARATOR) - && !env.getRegion().getTableDescriptor().getTableName().getNameAsString().startsWith( - PhoenixDatabaseMetaData.SYSTEM_CATALOG_SCHEMA + QueryConstants.NAMESPACE_SEPARATOR) - ) { + byte[] uuid = attributes.get(PhoenixIndexCodec.INDEX_UUID); + boolean useServerMetadata = uuid != null && uuid.length == 0; + if (useServerMetadata) { IndexMetaDataCache cacheFromPTable = getIndexMetaDataCacheFromPTable(env, attributes); if (cacheFromPTable != null) { return cacheFromPTable; } } - byte[] uuid = attributes.get(PhoenixIndexCodec.INDEX_UUID); if (uuid == null) { return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; } From 8cadcd9df1e32748d99e6fe65c7662f56fb23929 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Wed, 26 Nov 2025 16:38:38 +0530 Subject: [PATCH 05/10] checks --- .../index/IndexMetaDataCacheClient.java | 20 ++++++++++++++----- .../index/PhoenixIndexMetaDataBuilder.java | 8 ++++---- .../end2end/LocalIndexSplitMergeIT.java | 5 ++--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java index 393235b51fb..a83b0c04e14 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java @@ -19,6 +19,8 @@ import static org.apache.phoenix.query.QueryServices.INDEX_MUTATE_BATCH_SIZE_THRESHOLD_ATTRIB; import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; +import static org.apache.phoenix.query.QueryServices.SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED_ATTRIB; +import static org.apache.phoenix.query.QueryServicesOptions.DEFAULT_SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED; import static org.apache.phoenix.schema.types.PDataType.TRUE_BYTES; import java.sql.SQLException; @@ -128,12 +130,20 @@ public static ServerCache setMetaDataOnMutations(PhoenixConnection connection, P } boolean hasIndexMetaData = indexMetaDataPtr.getLength() > 0; ReadOnlyProps props = connection.getQueryServices().getProps(); - boolean useServerMetadata = props.getBoolean(INDEX_USE_SERVER_METADATA_ATTRIB, - QueryServicesOptions.DEFAULT_INDEX_USE_SERVER_METADATA) - && props.getBoolean(QueryServices.INDEX_REGION_OBSERVER_ENABLED_ATTRIB, - QueryServicesOptions.DEFAULT_INDEX_REGION_OBSERVER_ENABLED); if (hasIndexMetaData) { - if (useServerMetadata && table.getType() != PTableType.SYSTEM) { + boolean useServerMetadata = props.getBoolean(INDEX_USE_SERVER_METADATA_ATTRIB, + QueryServicesOptions.DEFAULT_INDEX_USE_SERVER_METADATA) + && props.getBoolean(QueryServices.INDEX_REGION_OBSERVER_ENABLED_ATTRIB, + QueryServicesOptions.DEFAULT_INDEX_REGION_OBSERVER_ENABLED) + && !props.getBoolean(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, + QueryServicesOptions.DEFAULT_IS_NAMESPACE_MAPPING_ENABLED); + boolean serverSideImmutableIndexes = + props.getBoolean(SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED_ATTRIB, + DEFAULT_SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED); + if ( + useServerMetadata && table.getType() != PTableType.SYSTEM + && (!table.isImmutableRows() || serverSideImmutableIndexes) + ) { LOGGER.trace("Using server-side metadata for table {}, not sending IndexMaintainer or UUID", table.getTableName()); uuidValue = ByteUtil.EMPTY_BYTE_ARRAY; diff --git a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java index 8127cafd324..9bfe7ab2517 100644 --- a/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java +++ b/phoenix-core-server/src/main/java/org/apache/phoenix/index/PhoenixIndexMetaDataBuilder.java @@ -73,16 +73,16 @@ private static IndexMetaDataCache getIndexMetaDataCache(RegionCoprocessorEnviron return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; } byte[] uuid = attributes.get(PhoenixIndexCodec.INDEX_UUID); - boolean useServerMetadata = uuid != null && uuid.length == 0; + if (uuid == null) { + return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; + } + boolean useServerMetadata = uuid.length == 0; if (useServerMetadata) { IndexMetaDataCache cacheFromPTable = getIndexMetaDataCacheFromPTable(env, attributes); if (cacheFromPTable != null) { return cacheFromPTable; } } - if (uuid == null) { - return IndexMetaDataCache.EMPTY_INDEX_META_DATA_CACHE; - } byte[] md = attributes.get(PhoenixIndexCodec.INDEX_PROTO_MD); if (md == null) { md = attributes.get(PhoenixIndexCodec.INDEX_MD); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java index 87f09e3adc6..e654fe0c19c 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/LocalIndexSplitMergeIT.java @@ -17,7 +17,6 @@ */ package org.apache.phoenix.end2end; -import static org.apache.phoenix.query.QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -77,8 +76,8 @@ private void createBaseTable(String tableName, String splits) throws SQLExceptio Connection conn = getConnectionForLocalIndexTest(); String ddl = "CREATE TABLE " + tableName + " (t_id VARCHAR NOT NULL,\n" + "k1 INTEGER NOT NULL,\n" + "k2 INTEGER NOT NULL,\n" + "k3 INTEGER,\n" + "v1 VARCHAR,\n" - + "CONSTRAINT pk PRIMARY KEY (t_id, k1, k2)) \"" + INDEX_USE_SERVER_METADATA_ATTRIB - + "\"=false" + " \n" + (splits != null ? (" split on " + splits) : ""); + + "CONSTRAINT pk PRIMARY KEY (t_id, k1, k2))\n" + + (splits != null ? (" split on " + splits) : ""); conn.createStatement().execute(ddl); conn.close(); } From 3d04232cd95b8e2308c119d86267d91ecf54d26e Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Fri, 28 Nov 2025 01:23:43 +0530 Subject: [PATCH 06/10] test changes --- .../end2end/ConcurrentMutationsIT.java | 4 +- .../end2end/UCFWithServerMetadataIT.java | 282 ++++++++++++++++++ 2 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsIT.java index a20a266871c..f18bc11fe1f 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConcurrentMutationsIT.java @@ -225,7 +225,7 @@ public void testSetIndexedColumnToNullAndValueAtSameTSWithStoreNulls2() throws E conn.close(); Timestamp expectedTimestamp; - ts = 1040; + ts = clock.time + 1; clock.time = ts; conn = DriverManager.getConnection(getUrl(), props); stmt = conn.prepareStatement("UPSERT INTO " + tableName + " VALUES('aa','aa',?, null)"); @@ -239,7 +239,7 @@ public void testSetIndexedColumnToNullAndValueAtSameTSWithStoreNulls2() throws E conn.commit(); conn.close(); - ts = 1050; + ts = clock.time + 1; clock.time = ts; conn = DriverManager.getConnection(getUrl(), props); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java new file mode 100644 index 00000000000..7cf5ddfe183 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.phoenix.end2end; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.protobuf.RpcCallback; +import com.google.protobuf.RpcController; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.phoenix.compile.ExplainPlan; +import org.apache.phoenix.compile.ExplainPlanAttributes; +import org.apache.phoenix.coprocessor.MetaDataEndpointImpl; +import org.apache.phoenix.coprocessor.generated.MetaDataProtos.GetTableRequest; +import org.apache.phoenix.coprocessor.generated.MetaDataProtos.MetaDataResponse; +import org.apache.phoenix.jdbc.PhoenixPreparedStatement; +import org.apache.phoenix.query.BaseTest; +import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.util.PropertiesUtil; +import org.apache.phoenix.util.ReadOnlyProps; +import org.apache.phoenix.util.TestUtil; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import org.apache.phoenix.thirdparty.com.google.common.collect.Maps; + +/** + * Test GetTable rpc calls for the combination of UCF and useServerMetadata. + */ +@Category(NeedsOwnMiniClusterTest.class) +@RunWith(Parameterized.class) +public class UCFWithServerMetadataIT extends BaseTest { + + private final String updateCacheFrequency; + private final boolean useServerMetadata; + private static final AtomicLong getTableCallCount = new AtomicLong(0); + + public UCFWithServerMetadataIT(String updateCacheFrequency, boolean useServerMetadata) { + this.updateCacheFrequency = updateCacheFrequency; + this.useServerMetadata = useServerMetadata; + } + + @Parameters(name = "UpdateCacheFrequency={0}, UseServerMetadata={1}") + public static Collection data() { + return Arrays.asList(new Object[][] { { "60000", true }, { "60000", false }, { "ALWAYS", true }, + { "ALWAYS", false } }); + } + + public static class TrackingMetaDataEndpointImpl extends MetaDataEndpointImpl { + + @Override + public void getTable(RpcController controller, GetTableRequest request, + RpcCallback done) { + getTableCallCount.incrementAndGet(); + super.getTable(controller, request, done); + } + } + + @BeforeClass + public static synchronized void doSetup() throws Exception { + Map serverProps = Maps.newHashMapWithExpectedSize(1); + Map clientProps = Maps.newHashMapWithExpectedSize(1); + setUpTestDriver(new ReadOnlyProps(serverProps.entrySet().iterator()), + new ReadOnlyProps(clientProps.entrySet().iterator())); + } + + @Before + public void setUp() { + getTableCallCount.set(0); + } + + @Test + public void testUpdateCacheFrequency() throws Exception { + String dataTableName = generateUniqueName(); + String coveredIndex1 = "CI1_" + generateUniqueName(); + String coveredIndex2 = "CI2_" + generateUniqueName(); + String uncoveredIndex1 = "UI1_" + generateUniqueName(); + String uncoveredIndex2 = "UI2_" + generateUniqueName(); + Properties props = PropertiesUtil.deepCopy(TestUtil.TEST_PROPERTIES); + props.setProperty(QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB, + Boolean.toString(useServerMetadata)); + try (Connection conn = DriverManager.getConnection(getUrl(), props)) { + conn.setAutoCommit(true); + attachCustomCoprocessor(conn); + String createTableDDL = + String.format("CREATE TABLE %s (id INTEGER PRIMARY KEY, name VARCHAR(50), " + + "age INTEGER, city VARCHAR(50), salary INTEGER, department VARCHAR(50)" + + ") UPDATE_CACHE_FREQUENCY=%s", dataTableName, updateCacheFrequency); + conn.createStatement().execute(createTableDDL); + conn.createStatement().execute(String + .format("CREATE INDEX %s ON %s (name) INCLUDE (age, city)", coveredIndex1, dataTableName)); + conn.createStatement().execute(String.format( + "CREATE INDEX %s ON %s (city) INCLUDE (salary, department)", coveredIndex2, dataTableName)); + conn.createStatement() + .execute(String.format("CREATE INDEX %s ON %s (age)", uncoveredIndex1, dataTableName)); + conn.createStatement() + .execute(String.format("CREATE INDEX %s ON %s (salary)", uncoveredIndex2, dataTableName)); + long startGetTableCalls = getTableCallCount.get(); + String upsertSQL = String.format( + "UPSERT INTO %s (id, name, age, city, salary, department) VALUES (?, ?, ?, ?, ?, ?)", + dataTableName); + PreparedStatement stmt = conn.prepareStatement(upsertSQL); + for (int i = 1; i <= 50; i++) { + stmt.setInt(1, i); + stmt.setString(2, "Name" + i); + stmt.setInt(3, 20 + (i % 40)); + stmt.setString(4, "City" + (i % 10)); + stmt.setInt(5, 30000 + (i * 1000)); + stmt.setString(6, "Dept" + (i % 5)); + stmt.executeUpdate(); + } + testDataTableReads(conn, dataTableName); + testCoveredIndexReads(conn, dataTableName, coveredIndex1, coveredIndex2); + testUncoveredIndexReads(conn, dataTableName, uncoveredIndex1, uncoveredIndex2); + testFullTableScan(conn, dataTableName); + long finalGetTableCalls = getTableCallCount.get(); + long actualGetTableCalls = finalGetTableCalls - startGetTableCalls; + int expectedCalls; + String caseKey = updateCacheFrequency + "_" + useServerMetadata; + switch (caseKey) { + case "60000_true": + expectedCalls = 1; + break; + case "60000_false": + expectedCalls = 0; + break; + case "ALWAYS_true": + expectedCalls = 232; + break; + case "ALWAYS_false": + expectedCalls = 182; + break; + default: + throw new IllegalArgumentException("Unexpected test case: " + caseKey); + } + assertEquals("Expected exact number of getTable() calls for case: " + caseKey, expectedCalls, + actualGetTableCalls); + } + } + + private void testFullTableScan(Connection conn, String dataTableName) throws SQLException { + String query = String.format("SELECT id, name, department FROM %s WHERE department = 'Dept1'", + dataTableName); + try (PreparedStatement stmt = conn.prepareStatement(query); + ResultSet rs = stmt.executeQuery()) { + int count = 0; + while (rs.next()) { + count++; + assertEquals("Dept1", rs.getString("department")); + } + assertEquals(10, count); + ExplainPlan plan = + stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); + ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertEquals("Should use data table for full scan", dataTableName, + planAttributes.getTableName()); + assertEquals("Should be a full scan", "FULL SCAN ", planAttributes.getExplainScanType()); + } + } + + private void attachCustomCoprocessor(Connection conn) throws Exception { + TestUtil.removeCoprocessor(conn, "SYSTEM.CATALOG", MetaDataEndpointImpl.class); + TestUtil.addCoprocessor(conn, "SYSTEM.CATALOG", TrackingMetaDataEndpointImpl.class); + } + + private void testDataTableReads(Connection conn, String dataTableName) throws SQLException { + String query = String.format("SELECT * FROM %s WHERE id BETWEEN 10 AND 15", dataTableName); + try (PreparedStatement stmt = conn.prepareStatement(query); + ResultSet rs = stmt.executeQuery()) { + int count = 0; + while (rs.next()) { + count++; + assertTrue(rs.getInt("id") >= 10 && rs.getInt("id") <= 15); + } + assertEquals(6, count); + ExplainPlan plan = + stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); + ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertEquals("Should use data table", dataTableName, planAttributes.getTableName()); + assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); + } + } + + private void testCoveredIndexReads(Connection conn, String dataTableName, String coveredIndex1, + String coveredIndex2) throws SQLException { + String query = + String.format("SELECT name, age, city FROM %s WHERE name = 'Name25'", dataTableName); + try (PreparedStatement stmt = conn.prepareStatement(query); + ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next()); + assertEquals("Name25", rs.getString("name")); + assertEquals(45, rs.getInt("age")); + assertEquals("City5", rs.getString("city")); + ExplainPlan plan = + stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); + ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertEquals("Should use covered index", coveredIndex1, planAttributes.getTableName()); + assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); + } + query = + String.format("SELECT city, salary, department FROM %s WHERE city = 'City3'", dataTableName); + try (PreparedStatement stmt = conn.prepareStatement(query); + ResultSet rs = stmt.executeQuery()) { + int count = 0; + while (rs.next()) { + count++; + assertEquals("City3", rs.getString("city")); + assertTrue(rs.getInt("salary") > 30000); + } + assertEquals(5, count); + ExplainPlan plan = + stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); + ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertEquals("Should use covered index", coveredIndex2, planAttributes.getTableName()); + assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); + } + } + + private void testUncoveredIndexReads(Connection conn, String dataTableName, + String uncoveredIndex1, String uncoveredIndex2) throws SQLException { + String query = String.format("SELECT /*+ INDEX(%s %s) */ id, name, age FROM %s WHERE age = 35", + dataTableName, uncoveredIndex1, dataTableName); + try (PreparedStatement stmt = conn.prepareStatement(query); + ResultSet rs = stmt.executeQuery()) { + int count = 0; + while (rs.next()) { + count++; + assertEquals(35, rs.getInt("age")); + } + assertTrue(count > 0); + ExplainPlan plan = + stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); + ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertEquals("Should use uncovered index", uncoveredIndex1, planAttributes.getTableName()); + assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); + } + query = + String.format("SELECT /*+ INDEX(%s %s) */ id, name, salary FROM %s WHERE salary = 45000", + dataTableName, uncoveredIndex2, dataTableName); + try (PreparedStatement stmt = conn.prepareStatement(query); + ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next()); + assertEquals(45000, rs.getInt("salary")); + assertEquals("Name15", rs.getString("name")); + ExplainPlan plan = + stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); + ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); + assertEquals("Should use uncovered index", uncoveredIndex2, planAttributes.getTableName()); + assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); + } + } + +} From 54fe87665daa8f4211c054bfdddd73153d6d7350 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Fri, 28 Nov 2025 16:17:44 +0530 Subject: [PATCH 07/10] more params --- .../end2end/UCFWithServerMetadataIT.java | 211 ++++++------------ 1 file changed, 71 insertions(+), 140 deletions(-) diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java index 7cf5ddfe183..40daab287fb 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java @@ -18,26 +18,24 @@ package org.apache.phoenix.end2end; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import com.google.protobuf.RpcCallback; import com.google.protobuf.RpcController; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicLong; -import org.apache.phoenix.compile.ExplainPlan; -import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.coprocessor.MetaDataEndpointImpl; +import org.apache.phoenix.coprocessor.ServerCachingEndpointImpl; import org.apache.phoenix.coprocessor.generated.MetaDataProtos.GetTableRequest; import org.apache.phoenix.coprocessor.generated.MetaDataProtos.MetaDataResponse; -import org.apache.phoenix.jdbc.PhoenixPreparedStatement; +import org.apache.phoenix.coprocessor.generated.ServerCachingProtos.AddServerCacheRequest; +import org.apache.phoenix.coprocessor.generated.ServerCachingProtos.AddServerCacheResponse; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.util.PropertiesUtil; @@ -51,8 +49,6 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; -import org.apache.phoenix.thirdparty.com.google.common.collect.Maps; - /** * Test GetTable rpc calls for the combination of UCF and useServerMetadata. */ @@ -62,17 +58,22 @@ public class UCFWithServerMetadataIT extends BaseTest { private final String updateCacheFrequency; private final boolean useServerMetadata; + private final boolean singleRowUpdate; private static final AtomicLong getTableCallCount = new AtomicLong(0); + private static final AtomicLong addServerCacheCallCount = new AtomicLong(0); - public UCFWithServerMetadataIT(String updateCacheFrequency, boolean useServerMetadata) { + public UCFWithServerMetadataIT(String updateCacheFrequency, boolean useServerMetadata, + boolean singleRowUpdate) { this.updateCacheFrequency = updateCacheFrequency; this.useServerMetadata = useServerMetadata; + this.singleRowUpdate = singleRowUpdate; } - @Parameters(name = "UpdateCacheFrequency={0}, UseServerMetadata={1}") + @Parameters(name = "UpdateCacheFrequency={0}, UseServerMetadata={1}, SingleRowUpdate={2}") public static Collection data() { - return Arrays.asList(new Object[][] { { "60000", true }, { "60000", false }, { "ALWAYS", true }, - { "ALWAYS", false } }); + return Arrays.asList(new Object[][] { { "60000", true, true }, { "60000", true, false }, + { "60000", false, true }, { "60000", false, false }, { "ALWAYS", true, true }, + { "ALWAYS", true, false }, { "ALWAYS", false, true }, { "ALWAYS", false, false } }); } public static class TrackingMetaDataEndpointImpl extends MetaDataEndpointImpl { @@ -85,17 +86,27 @@ public void getTable(RpcController controller, GetTableRequest request, } } + public static class TrackingServerCachingEndpointImpl extends ServerCachingEndpointImpl { + + @Override + public void addServerCache(RpcController controller, AddServerCacheRequest request, + RpcCallback done) { + addServerCacheCallCount.incrementAndGet(); + super.addServerCache(controller, request, done); + } + } + @BeforeClass public static synchronized void doSetup() throws Exception { - Map serverProps = Maps.newHashMapWithExpectedSize(1); - Map clientProps = Maps.newHashMapWithExpectedSize(1); - setUpTestDriver(new ReadOnlyProps(serverProps.entrySet().iterator()), - new ReadOnlyProps(clientProps.entrySet().iterator())); + Map props = new HashMap<>(1); + props.put(QueryServices.TASK_HANDLING_INITIAL_DELAY_MS_ATTRIB, Long.toString(Long.MAX_VALUE)); + setUpTestDriver(new ReadOnlyProps(props)); } @Before public void setUp() { getTableCallCount.set(0); + addServerCacheCallCount.set(0); } @Test @@ -109,13 +120,12 @@ public void testUpdateCacheFrequency() throws Exception { props.setProperty(QueryServices.INDEX_USE_SERVER_METADATA_ATTRIB, Boolean.toString(useServerMetadata)); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { - conn.setAutoCommit(true); - attachCustomCoprocessor(conn); String createTableDDL = String.format("CREATE TABLE %s (id INTEGER PRIMARY KEY, name VARCHAR(50), " + "age INTEGER, city VARCHAR(50), salary INTEGER, department VARCHAR(50)" + ") UPDATE_CACHE_FREQUENCY=%s", dataTableName, updateCacheFrequency); conn.createStatement().execute(createTableDDL); + attachCustomCoprocessor(conn, dataTableName); conn.createStatement().execute(String .format("CREATE INDEX %s ON %s (name) INCLUDE (age, city)", coveredIndex1, dataTableName)); conn.createStatement().execute(String.format( @@ -124,12 +134,14 @@ public void testUpdateCacheFrequency() throws Exception { .execute(String.format("CREATE INDEX %s ON %s (age)", uncoveredIndex1, dataTableName)); conn.createStatement() .execute(String.format("CREATE INDEX %s ON %s (salary)", uncoveredIndex2, dataTableName)); - long startGetTableCalls = getTableCallCount.get(); String upsertSQL = String.format( "UPSERT INTO %s (id, name, age, city, salary, department) VALUES (?, ?, ?, ?, ?, ?)", dataTableName); + int totalRows = 52; + int batchSize = 8; PreparedStatement stmt = conn.prepareStatement(upsertSQL); - for (int i = 1; i <= 50; i++) { + long startGetTableCalls = getTableCallCount.get(); + for (int i = 1; i <= totalRows; i++) { stmt.setInt(1, i); stmt.setString(2, "Name" + i); stmt.setInt(3, 20 + (i % 40)); @@ -137,146 +149,65 @@ public void testUpdateCacheFrequency() throws Exception { stmt.setInt(5, 30000 + (i * 1000)); stmt.setString(6, "Dept" + (i % 5)); stmt.executeUpdate(); + if (singleRowUpdate) { + conn.commit(); + } else if (i % batchSize == 0) { + conn.commit(); + } } - testDataTableReads(conn, dataTableName); - testCoveredIndexReads(conn, dataTableName, coveredIndex1, coveredIndex2); - testUncoveredIndexReads(conn, dataTableName, uncoveredIndex1, uncoveredIndex2); - testFullTableScan(conn, dataTableName); - long finalGetTableCalls = getTableCallCount.get(); - long actualGetTableCalls = finalGetTableCalls - startGetTableCalls; + if (!singleRowUpdate) { + conn.commit(); + } + long actualGetTableCalls = getTableCallCount.get() - startGetTableCalls; int expectedCalls; - String caseKey = updateCacheFrequency + "_" + useServerMetadata; + String caseKey = updateCacheFrequency + "_" + useServerMetadata + "_" + singleRowUpdate; switch (caseKey) { - case "60000_true": + case "60000_true_true": + case "60000_true_false": expectedCalls = 1; break; - case "60000_false": + case "60000_false_true": + case "60000_false_false": expectedCalls = 0; break; - case "ALWAYS_true": - expectedCalls = 232; + case "ALWAYS_true_true": + expectedCalls = totalRows * 2; + break; + case "ALWAYS_true_false": + expectedCalls = (int) Math.ceil((double) totalRows / batchSize) * 2; break; - case "ALWAYS_false": - expectedCalls = 182; + case "ALWAYS_false_true": + expectedCalls = totalRows; + break; + case "ALWAYS_false_false": + expectedCalls = (int) Math.ceil((double) totalRows / batchSize); break; default: throw new IllegalArgumentException("Unexpected test case: " + caseKey); } assertEquals("Expected exact number of getTable() calls for case: " + caseKey, expectedCalls, actualGetTableCalls); - } - } - - private void testFullTableScan(Connection conn, String dataTableName) throws SQLException { - String query = String.format("SELECT id, name, department FROM %s WHERE department = 'Dept1'", - dataTableName); - try (PreparedStatement stmt = conn.prepareStatement(query); - ResultSet rs = stmt.executeQuery()) { - int count = 0; - while (rs.next()) { - count++; - assertEquals("Dept1", rs.getString("department")); + long actualAddServerCacheCalls = addServerCacheCallCount.get(); + int expectedAddServerCacheCalls; + switch (caseKey) { + case "60000_false_false": + case "ALWAYS_false_false": + expectedAddServerCacheCalls = (int) Math.ceil((double) totalRows / batchSize); + break; + default: + expectedAddServerCacheCalls = 0; + break; } - assertEquals(10, count); - ExplainPlan plan = - stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("Should use data table for full scan", dataTableName, - planAttributes.getTableName()); - assertEquals("Should be a full scan", "FULL SCAN ", planAttributes.getExplainScanType()); + assertEquals("Expected exact number of addServerCache() calls for case: " + caseKey, + expectedAddServerCacheCalls, actualAddServerCacheCalls); } } - private void attachCustomCoprocessor(Connection conn) throws Exception { + private void attachCustomCoprocessor(Connection conn, String dataTableName) throws Exception { TestUtil.removeCoprocessor(conn, "SYSTEM.CATALOG", MetaDataEndpointImpl.class); TestUtil.addCoprocessor(conn, "SYSTEM.CATALOG", TrackingMetaDataEndpointImpl.class); - } - - private void testDataTableReads(Connection conn, String dataTableName) throws SQLException { - String query = String.format("SELECT * FROM %s WHERE id BETWEEN 10 AND 15", dataTableName); - try (PreparedStatement stmt = conn.prepareStatement(query); - ResultSet rs = stmt.executeQuery()) { - int count = 0; - while (rs.next()) { - count++; - assertTrue(rs.getInt("id") >= 10 && rs.getInt("id") <= 15); - } - assertEquals(6, count); - ExplainPlan plan = - stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("Should use data table", dataTableName, planAttributes.getTableName()); - assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); - } - } - - private void testCoveredIndexReads(Connection conn, String dataTableName, String coveredIndex1, - String coveredIndex2) throws SQLException { - String query = - String.format("SELECT name, age, city FROM %s WHERE name = 'Name25'", dataTableName); - try (PreparedStatement stmt = conn.prepareStatement(query); - ResultSet rs = stmt.executeQuery()) { - assertTrue(rs.next()); - assertEquals("Name25", rs.getString("name")); - assertEquals(45, rs.getInt("age")); - assertEquals("City5", rs.getString("city")); - ExplainPlan plan = - stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("Should use covered index", coveredIndex1, planAttributes.getTableName()); - assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); - } - query = - String.format("SELECT city, salary, department FROM %s WHERE city = 'City3'", dataTableName); - try (PreparedStatement stmt = conn.prepareStatement(query); - ResultSet rs = stmt.executeQuery()) { - int count = 0; - while (rs.next()) { - count++; - assertEquals("City3", rs.getString("city")); - assertTrue(rs.getInt("salary") > 30000); - } - assertEquals(5, count); - ExplainPlan plan = - stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("Should use covered index", coveredIndex2, planAttributes.getTableName()); - assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); - } - } - - private void testUncoveredIndexReads(Connection conn, String dataTableName, - String uncoveredIndex1, String uncoveredIndex2) throws SQLException { - String query = String.format("SELECT /*+ INDEX(%s %s) */ id, name, age FROM %s WHERE age = 35", - dataTableName, uncoveredIndex1, dataTableName); - try (PreparedStatement stmt = conn.prepareStatement(query); - ResultSet rs = stmt.executeQuery()) { - int count = 0; - while (rs.next()) { - count++; - assertEquals(35, rs.getInt("age")); - } - assertTrue(count > 0); - ExplainPlan plan = - stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("Should use uncovered index", uncoveredIndex1, planAttributes.getTableName()); - assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); - } - query = - String.format("SELECT /*+ INDEX(%s %s) */ id, name, salary FROM %s WHERE salary = 45000", - dataTableName, uncoveredIndex2, dataTableName); - try (PreparedStatement stmt = conn.prepareStatement(query); - ResultSet rs = stmt.executeQuery()) { - assertTrue(rs.next()); - assertEquals(45000, rs.getInt("salary")); - assertEquals("Name15", rs.getString("name")); - ExplainPlan plan = - stmt.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); - ExplainPlanAttributes planAttributes = plan.getPlanStepsAsAttributes(); - assertEquals("Should use uncovered index", uncoveredIndex2, planAttributes.getTableName()); - assertEquals("Should be a range scan", "RANGE SCAN ", planAttributes.getExplainScanType()); - } + TestUtil.removeCoprocessor(conn, dataTableName, ServerCachingEndpointImpl.class); + TestUtil.addCoprocessor(conn, dataTableName, TrackingServerCachingEndpointImpl.class); } } From f363497fecbdfe95ed31dbb7b89ce15dcff8d9fb Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Fri, 28 Nov 2025 17:00:09 +0530 Subject: [PATCH 08/10] changes with UCF --- .../index/IndexMetaDataCacheClient.java | 24 ++++++++++++++++--- .../end2end/UCFWithServerMetadataIT.java | 2 +- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java index a83b0c04e14..2878c21955a 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java @@ -140,16 +140,34 @@ public static ServerCache setMetaDataOnMutations(PhoenixConnection connection, P boolean serverSideImmutableIndexes = props.getBoolean(SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED_ATTRIB, DEFAULT_SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED); + boolean useServerCacheRpc = + useIndexMetadataCache(connection, mutations, indexMetaDataPtr.getLength() + txState.length); + long updateCacheFreq = table.getUpdateCacheFrequency(); + // PHOENIX-7727 Eliminate IndexMetadataCache RPCs by leveraging server PTable cache and + // retrieve IndexMaintainer objects for each active index from the PTable object. + // To optimize rpc calls, use it only when all of these conditions are met: + // 1. User server metadata feature is enabled (enabled by default). + // 2. New index design is used (IndexRegionObserver coproc). + // 3. Schema namespace mapping is disabled. + // 4. Table is not of type System. + // 5. Either table has mutable indexes or server side handling of immutable indexes is + // enabled. + // 6. Table's UPDATE_CACHE_FREQUENCY is not ALWAYS. This ensures IndexRegionObserver + // does not have to make additional getTable() rpc call with each batchMutate() rpc call. + // 7. Table's UPDATE_CACHE_FREQUENCY is ALWAYS but addServerCache() rpc call is needed + // due to the size of mutations. Unless expensive addServerCache() rpc call is required, + // client can attach index maintainer mutation attribute so that IndexRegionObserver + // does not have to make additional getTable() rpc call with each batchMutate() rpc call + // with small mutation size (size < phoenix.index.mutableBatchSizeThreshold value). if ( useServerMetadata && table.getType() != PTableType.SYSTEM && (!table.isImmutableRows() || serverSideImmutableIndexes) + && (updateCacheFreq > 0 || useServerCacheRpc) ) { LOGGER.trace("Using server-side metadata for table {}, not sending IndexMaintainer or UUID", table.getTableName()); uuidValue = ByteUtil.EMPTY_BYTE_ARRAY; - } else if ( - useIndexMetadataCache(connection, mutations, indexMetaDataPtr.getLength() + txState.length) - ) { + } else if (useServerCacheRpc) { IndexMetaDataCacheClient client = new IndexMetaDataCacheClient(connection, table); cache = client.addIndexMetadataCache(mutations, indexMetaDataPtr, txState); uuidValue = cache.getId(); diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java index 40daab287fb..6a19bec2e69 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UCFWithServerMetadataIT.java @@ -171,7 +171,7 @@ public void testUpdateCacheFrequency() throws Exception { expectedCalls = 0; break; case "ALWAYS_true_true": - expectedCalls = totalRows * 2; + expectedCalls = totalRows; break; case "ALWAYS_true_false": expectedCalls = (int) Math.ceil((double) totalRows / batchSize) * 2; From 251b9ab7d718abd77e9a12c0c548cc1c9d1229f8 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Sun, 30 Nov 2025 15:53:13 +0530 Subject: [PATCH 09/10] update conditions --- .../index/IndexMetaDataCacheClient.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java index 2878c21955a..3b69e516695 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java @@ -131,34 +131,40 @@ public static ServerCache setMetaDataOnMutations(PhoenixConnection connection, P boolean hasIndexMetaData = indexMetaDataPtr.getLength() > 0; ReadOnlyProps props = connection.getQueryServices().getProps(); if (hasIndexMetaData) { + List indexes = table.getIndexes(); + boolean hasActiveIndexes = + indexes != null && indexes.stream().anyMatch(IndexMaintainer::sendIndexMaintainer); boolean useServerMetadata = props.getBoolean(INDEX_USE_SERVER_METADATA_ATTRIB, QueryServicesOptions.DEFAULT_INDEX_USE_SERVER_METADATA) && props.getBoolean(QueryServices.INDEX_REGION_OBSERVER_ENABLED_ATTRIB, - QueryServicesOptions.DEFAULT_INDEX_REGION_OBSERVER_ENABLED) - && !props.getBoolean(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, - QueryServicesOptions.DEFAULT_IS_NAMESPACE_MAPPING_ENABLED); + QueryServicesOptions.DEFAULT_INDEX_REGION_OBSERVER_ENABLED); boolean serverSideImmutableIndexes = props.getBoolean(SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED_ATTRIB, DEFAULT_SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED); boolean useServerCacheRpc = - useIndexMetadataCache(connection, mutations, indexMetaDataPtr.getLength() + txState.length); + useIndexMetadataCache(connection, mutations, indexMetaDataPtr.getLength() + txState.length) + && hasActiveIndexes; long updateCacheFreq = table.getUpdateCacheFrequency(); // PHOENIX-7727 Eliminate IndexMetadataCache RPCs by leveraging server PTable cache and // retrieve IndexMaintainer objects for each active index from the PTable object. // To optimize rpc calls, use it only when all of these conditions are met: - // 1. User server metadata feature is enabled (enabled by default). + // 1. Use server metadata feature is enabled (enabled by default). // 2. New index design is used (IndexRegionObserver coproc). - // 3. Schema namespace mapping is disabled. - // 4. Table is not of type System. - // 5. Either table has mutable indexes or server side handling of immutable indexes is + // 3. Table is not of type System. + // 4. Either table has mutable indexes or server side handling of immutable indexes is // enabled. - // 6. Table's UPDATE_CACHE_FREQUENCY is not ALWAYS. This ensures IndexRegionObserver + // 5. Table's UPDATE_CACHE_FREQUENCY is not ALWAYS. This ensures IndexRegionObserver // does not have to make additional getTable() rpc call with each batchMutate() rpc call. - // 7. Table's UPDATE_CACHE_FREQUENCY is ALWAYS but addServerCache() rpc call is needed + // 6. Table's UPDATE_CACHE_FREQUENCY is ALWAYS but addServerCache() rpc call is needed // due to the size of mutations. Unless expensive addServerCache() rpc call is required, // client can attach index maintainer mutation attribute so that IndexRegionObserver // does not have to make additional getTable() rpc call with each batchMutate() rpc call // with small mutation size (size < phoenix.index.mutableBatchSizeThreshold value). + // If above conditions do not match and if the mutation size is greater than + // "phoenix.index.mutableBatchSizeThreshold" value, however if none of the data table + // indexes need to be sent to server (only index in state other than DISABLE, + // CREATE_DISABLE, PENDING_ACTIVE need to be sent to server), do not use expensive + // addServerCache() rpc call. if ( useServerMetadata && table.getType() != PTableType.SYSTEM && (!table.isImmutableRows() || serverSideImmutableIndexes) From 4dc9a8b42fe13ed775b6f753d1855f46d36ac3b8 Mon Sep 17 00:00:00 2001 From: Viraj Jasani Date: Sun, 30 Nov 2025 15:56:54 +0530 Subject: [PATCH 10/10] change var --- .../org/apache/phoenix/index/IndexMetaDataCacheClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java index 3b69e516695..8384bf8fe58 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/index/IndexMetaDataCacheClient.java @@ -132,7 +132,7 @@ public static ServerCache setMetaDataOnMutations(PhoenixConnection connection, P ReadOnlyProps props = connection.getQueryServices().getProps(); if (hasIndexMetaData) { List indexes = table.getIndexes(); - boolean hasActiveIndexes = + boolean sendIndexMaintainers = indexes != null && indexes.stream().anyMatch(IndexMaintainer::sendIndexMaintainer); boolean useServerMetadata = props.getBoolean(INDEX_USE_SERVER_METADATA_ATTRIB, QueryServicesOptions.DEFAULT_INDEX_USE_SERVER_METADATA) @@ -143,7 +143,7 @@ public static ServerCache setMetaDataOnMutations(PhoenixConnection connection, P DEFAULT_SERVER_SIDE_IMMUTABLE_INDEXES_ENABLED); boolean useServerCacheRpc = useIndexMetadataCache(connection, mutations, indexMetaDataPtr.getLength() + txState.length) - && hasActiveIndexes; + && sendIndexMaintainers; long updateCacheFreq = table.getUpdateCacheFrequency(); // PHOENIX-7727 Eliminate IndexMetadataCache RPCs by leveraging server PTable cache and // retrieve IndexMaintainer objects for each active index from the PTable object.