This is an automated email from the ASF dual-hosted git repository. vjasani pushed a commit to branch 5.2 in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/5.2 by this push: new 0d9a13812a PHOENIX-7625 Adding query plan information to connection Activity Logger (#2253) 0d9a13812a is described below commit 0d9a13812afa175226dcb8115a1ba154733c4a8a Author: vikas meka <vm...@salesforce.com> AuthorDate: Thu Jul 31 14:13:49 2025 -0700 PHOENIX-7625 Adding query plan information to connection Activity Logger (#2253) --- .../org/apache/phoenix/jdbc/PhoenixStatement.java | 48 +++++++++++++++++++++ .../org/apache/phoenix/log/ActivityLogInfo.java | 3 +- .../phoenix/log/ConnectionActivityLogger.java | 9 ++++ .../org/apache/phoenix/query/QueryServices.java | 2 + .../apache/phoenix/query/QueryServicesOptions.java | 4 ++ .../java/org/apache/phoenix/util/QueryUtil.java | 2 + .../org/apache/phoenix/end2end/ConnectionIT.java | 34 +++++++++++++++ .../phoenix/jdbc/LoggingConnectionLimiterIT.java | 50 ++++++++++++++++++++++ .../jdbc/LoggingSingleConnectionLimiterIT.java | 2 + 9 files changed, 153 insertions(+), 1 deletion(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java index 2ea156e180..768f9c62f2 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java @@ -42,6 +42,7 @@ import static org.apache.phoenix.monitoring.MetricType.UPSERT_FAILED_SQL_COUNTER import static org.apache.phoenix.monitoring.MetricType.UPSERT_SQL_COUNTER; import static org.apache.phoenix.monitoring.MetricType.UPSERT_SQL_QUERY_TIME; import static org.apache.phoenix.monitoring.MetricType.UPSERT_SUCCESS_SQL_COUNTER; +import static org.apache.phoenix.query.QueryServices.CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED; import java.io.File; import java.io.IOException; @@ -64,6 +65,8 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.hadoop.conf.Configuration; @@ -71,6 +74,7 @@ import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellComparator; +import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.client.Consistency; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.util.Pair; @@ -87,6 +91,7 @@ import org.apache.phoenix.compile.DeclareCursorCompiler; import org.apache.phoenix.compile.DeleteCompiler; import org.apache.phoenix.compile.DropSequenceCompiler; import org.apache.phoenix.compile.ExplainPlan; +import org.apache.phoenix.compile.ExplainPlanAttributes; import org.apache.phoenix.compile.ExpressionProjector; import org.apache.phoenix.compile.GroupByCompiler.GroupBy; import org.apache.phoenix.compile.ListJarsQueryPlan; @@ -288,12 +293,16 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable private int maxRows; private int fetchSize = -1; private int queryTimeoutMillis; + private boolean explainPlanLoggingEnabled; // Caching per Statement protected final Calendar localCalendar = Calendar.getInstance(); public PhoenixStatement(PhoenixConnection connection) { this.connection = connection; this.queryTimeoutMillis = getDefaultQueryTimeoutMillis(); + this.explainPlanLoggingEnabled = connection.getQueryServices().getProps() + .getBoolean(CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED, + QueryServicesOptions.DEFAULT_CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED); } /** @@ -388,6 +397,9 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable LOGGER.debug(LogUtil.addCustomAnnotations( "Explain plan: " + explainPlan, connection)); } + if (explainPlanLoggingEnabled) { + updateExplainPlanInformation(plan); + } StatementContext context = plan.getContext(); context.setQueryLogger(queryLogger); if (queryLogger.isDebugEnabled()) { @@ -2605,4 +2617,40 @@ public class PhoenixStatement implements PhoenixMonitoredStatement, SQLCloseable return localCalendar; } + private void updateExplainPlanInformation(QueryPlan plan) throws SQLException { + if ( + plan == null || !getConnection().getActivityLogger() + .isLevelEnabled(ActivityLogInfo.EXPLAIN_PLAN.getLogLevel()) + ) { + return; + } + + ExplainPlan explainPlan = plan.getExplainPlan(); + ExplainPlanAttributes explainPlanAttributes = explainPlan.getPlanStepsAsAttributes(); + + List<HRegionLocation> location = explainPlanAttributes.getRegionLocations(); + String regionInfo = getRegionInfo(location); + + String sb = Stream.of(explainPlanAttributes.getExplainScanType(), regionInfo) + .collect(Collectors.joining(",")); + updateActivityOnConnection(ActivityLogInfo.EXPLAIN_PLAN, sb); + } + + private String getRegionInfo(List<HRegionLocation> location) { + if (location == null || location.isEmpty()) { + return ""; + } + + String regions = + location.stream().map(regionLocation -> regionLocation.getRegion().getEncodedName()) + .collect(Collectors.joining(",")); + + String hostnames = location.stream() + .map(HRegionLocation::getHostname) + .collect(Collectors.joining(",")); + + return QueryUtil.REGIONS + "={" + regions + "}," + + QueryUtil.HOSTNAMES + "={" + hostnames + "}"; + } + } diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/log/ActivityLogInfo.java b/phoenix-core-client/src/main/java/org/apache/phoenix/log/ActivityLogInfo.java index 3baf44754d..3fa9b2fb8a 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/log/ActivityLogInfo.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/log/ActivityLogInfo.java @@ -34,7 +34,8 @@ public enum ActivityLogInfo { REQUEST_ID("r", LogLevel.INFO,PVarchar.INSTANCE), TABLE_NAME("n", LogLevel.INFO,PVarchar.INSTANCE), OP_NAME("o", LogLevel.INFO,PVarchar.INSTANCE), - OP_STMTS("#", LogLevel.INFO, PInteger.INSTANCE); + OP_STMTS("#", LogLevel.INFO, PInteger.INSTANCE), + EXPLAIN_PLAN("ep", LogLevel.INFO, PVarchar.INSTANCE); public final String shortName; public final LogLevel logLevel; diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/log/ConnectionActivityLogger.java b/phoenix-core-client/src/main/java/org/apache/phoenix/log/ConnectionActivityLogger.java index ee2a2a8af4..00e89eed26 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/log/ConnectionActivityLogger.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/log/ConnectionActivityLogger.java @@ -147,4 +147,13 @@ public class ConnectionActivityLogger { return this.logLevel != null && logLevel != LogLevel.OFF ? logLevel.ordinal() <= this.logLevel.ordinal() : false; } + + /** + * Get the Explain plan information. + */ + public String getExplainPlanInfo() { + return isLevelEnabled(ActivityLogInfo.EXPLAIN_PLAN.getLogLevel()) + ? activityList.get(ActivityLogInfo.EXPLAIN_PLAN.ordinal()) + : null; + } } 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 ea95e05f2b..3df7893f06 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 @@ -304,6 +304,8 @@ public interface QueryServices extends SQLCloseable { "phoenix.connection.activity.logging.enabled"; public static final String CONNECTION_ACTIVITY_LOGGING_INTERVAL = "phoenix.connection.activity.logging.interval"; + String CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED = + "phoenix.connection.activity.logging.explain.plan.enabled"; public static final String DEFAULT_COLUMN_ENCODED_BYTES_ATRRIB = "phoenix.default.column.encoded.bytes.attrib"; public static final String DEFAULT_IMMUTABLE_STORAGE_SCHEME_ATTRIB = "phoenix.default.immutable.storage.scheme"; public static final String DEFAULT_MULTITENANT_IMMUTABLE_STORAGE_SCHEME_ATTRIB = "phoenix.default.multitenant.immutable.storage.scheme"; 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 5c9210ef20..4c01c85d31 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 @@ -29,6 +29,7 @@ import static org.apache.phoenix.query.QueryServices.CLIENT_SPOOL_THRESHOLD_BYTE import static org.apache.phoenix.query.QueryServices.COLLECT_REQUEST_LEVEL_METRICS; import static org.apache.phoenix.query.QueryServices.COMMIT_STATS_ASYNC; import static org.apache.phoenix.query.QueryServices.CONNECTION_ACTIVITY_LOGGING_ENABLED; +import static org.apache.phoenix.query.QueryServices.CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED; import static org.apache.phoenix.query.QueryServices.CONNECTION_ACTIVITY_LOGGING_INTERVAL; import static org.apache.phoenix.query.QueryServices.CONNECTION_QUERY_SERVICE_HISTOGRAM_SIZE_RANGES; import static org.apache.phoenix.query.QueryServices.CONNECTION_QUERY_SERVICE_METRICS_ENABLED; @@ -368,6 +369,7 @@ public class QueryServicesOptions { public static final int DEFAULT_INTERNAL_CONNECTION_MAX_ALLOWED_CONNECTIONS = 0; public static final boolean DEFAULT_CONNECTION_ACTIVITY_LOGGING_ENABLED = false; + public static final boolean DEFAULT_CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED = false; public static final int DEFAULT_CONNECTION_ACTIVITY_LOGGING_INTERVAL_IN_MINS = 15; public static final boolean DEFAULT_STATS_COLLECTION_ENABLED = true; public static final boolean DEFAULT_USE_STATS_FOR_PARALLELIZATION = true; @@ -546,6 +548,8 @@ public class QueryServicesOptions { DEFAULT_SERVER_MERGE_FOR_UNCOVERED_INDEX) .setIfUnset(MAX_IN_LIST_SKIP_SCAN_SIZE, DEFAULT_MAX_IN_LIST_SKIP_SCAN_SIZE) .setIfUnset(CONNECTION_ACTIVITY_LOGGING_ENABLED, DEFAULT_CONNECTION_ACTIVITY_LOGGING_ENABLED) + .setIfUnset(CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED, + DEFAULT_CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED) .setIfUnset(CONNECTION_ACTIVITY_LOGGING_INTERVAL, DEFAULT_CONNECTION_ACTIVITY_LOGGING_INTERVAL_IN_MINS) .setIfUnset(CQSI_THREAD_POOL_ENABLED, DEFAULT_CQSI_THREAD_POOL_ENABLED) .setIfUnset(CQSI_THREAD_POOL_KEEP_ALIVE_SECONDS, diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/util/QueryUtil.java b/phoenix-core-client/src/main/java/org/apache/phoenix/util/QueryUtil.java index 52e4f6aa0e..af8bf049b9 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/QueryUtil.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/QueryUtil.java @@ -136,6 +136,8 @@ public final class QueryUtil { public static final int DATA_TYPE_NAME_POSITION = 6; public static final String IS_SERVER_CONNECTION = "IS_SERVER_CONNECTION"; + public static final String REGIONS = "regions"; + public static final String HOSTNAMES = "hostnames"; private static final String SELECT = "SELECT"; private static final String FROM = "FROM"; private static final String WHERE = "WHERE"; diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConnectionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConnectionIT.java index 87cf8649e3..451afc09df 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConnectionIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConnectionIT.java @@ -20,6 +20,7 @@ package org.apache.phoenix.end2end; import static org.apache.phoenix.query.BaseTest.setUpConfigForMiniCluster; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assume.assumeTrue; import java.sql.Connection; @@ -159,4 +160,37 @@ public class ConnectionIT { } } + + @Test + public void testQueryPlanIsNullByDefault() throws SQLException { + // Test that query plan is null by default even after data operations + String tableName = "TEST_QUERY_PLAN_" + tableCounter++; + + try (PhoenixConnection conn = (PhoenixConnection) DriverManager.getConnection("jdbc:phoenix")) { + // Initially, query plan should be null (disabled by default) + String initialQueryPlan = conn.getActivityLogger().getExplainPlanInfo(); + assertNull("Query plan should be null by default (disabled)", initialQueryPlan); + + // Create table and load data + try (Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE " + tableName + " (id INTEGER PRIMARY KEY, name VARCHAR)"); + stmt.execute("UPSERT INTO " + tableName + " VALUES (1, 'test1')"); + stmt.execute("UPSERT INTO " + tableName + " VALUES (2, 'test2')"); + conn.commit(); + } + + // Execute a query - query plan should still be null since it's disabled by default + try (Statement stmt = conn.createStatement()) { + ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName); + while (rs.next()) { + // Consume the result set + } + + // Query plan should still be null since it's disabled by default + String queryPlan = conn.getActivityLogger().getExplainPlanInfo(); + assertNull("Query plan should be null after query execution (disabled by default)", + queryPlan); + } + } + } } diff --git a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingConnectionLimiterIT.java b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingConnectionLimiterIT.java index 3a313f4973..9b02e17bc1 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingConnectionLimiterIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingConnectionLimiterIT.java @@ -31,6 +31,7 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.PreparedStatement; +import java.sql.Statement; import java.sql.Timestamp; import java.util.HashSet; import java.util.ArrayList; @@ -199,6 +200,55 @@ public abstract class LoggingConnectionLimiterIT extends BaseTest { } + @Test + public void testQueryExplainPlan() throws Exception { + + String query = "SELECT * FROM " + tableName; // FULL SCAN + + if (getConnection() instanceof PhoenixConnection) { + try (PhoenixConnection pconn = getConnection().unwrap(PhoenixConnection.class); + Statement stmt = pconn.createStatement()) { + ResultSet rs = stmt.executeQuery(query); + while (rs.next()) { + // do nothing + } + boolean queryPlanFound = false; + String queryPlan = pconn.getActivityLogger().getExplainPlanInfo(); + if ( + queryPlan != null && queryPlan.contains("FULL SCAN") && queryPlan.contains("regions=") + && queryPlan.contains("hostnames=") + ) { + queryPlanFound = true; + + // Extract regions and hostnames efficiently + String regions = extractBetweenBraces(queryPlan, "regions={"); + String hostnames = extractBetweenBraces(queryPlan, "hostnames={"); + + assertFalse("Regions should not be empty", regions.trim().isEmpty()); + assertFalse("Hostnames should not be empty", hostnames.trim().isEmpty()); + + } + assertTrue("Query plan should contain FULL SCAN, regions, and hostnames", queryPlanFound); + } + try (PhoenixConnection pconn = getConnection().unwrap(PhoenixConnection.class);) { + loadData(pconn, "PhoenixTest", "1", 10, 2); + String queryPlan = pconn.getActivityLogger().getExplainPlanInfo(); + assertTrue("Query plan should be empty for non-query operations", queryPlan.isEmpty()); + } + } else { + // for HA case, ignoring as parallelPhoenixConnection object doesn't have activity Logger + // object defined. + assertTrue(getConnection() instanceof ParallelPhoenixConnection); + } + + } + + private String extractBetweenBraces(String text, String marker) { + int start = text.indexOf(marker) + marker.length(); + int end = text.indexOf("}", start); + return text.substring(start, end); + } + protected abstract ConnectionLimiter getConnectionLimiter() throws Exception ; protected int runSampleActivity(ActivityType activityType, int clientPool, int clientQueue, int numRows, int connNotClosed) throws Exception { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingSingleConnectionLimiterIT.java b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingSingleConnectionLimiterIT.java index fb0ad2871d..0d20b20a4d 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingSingleConnectionLimiterIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingSingleConnectionLimiterIT.java @@ -64,6 +64,7 @@ public class LoggingSingleConnectionLimiterIT extends LoggingConnectionLimiterIT conf.set(QueryServices.INTERNAL_CONNECTION_MAX_ALLOWED_CONNECTIONS, String.valueOf(20)); conf.set(PhoenixHAExecutorServiceProvider.HA_MAX_POOL_SIZE, String.valueOf(5)); conf.set(PhoenixHAExecutorServiceProvider.HA_MAX_QUEUE_SIZE, String.valueOf(30)); + conf.set(QueryServices.CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED, String.valueOf(true)); return conf; } @@ -74,6 +75,7 @@ public class LoggingSingleConnectionLimiterIT extends LoggingConnectionLimiterIT conf.set(QueryServices.INTERNAL_CONNECTION_MAX_ALLOWED_CONNECTIONS, String.valueOf(20)); conf.set(PhoenixHAExecutorServiceProvider.HA_MAX_POOL_SIZE, String.valueOf(5)); conf.set(PhoenixHAExecutorServiceProvider.HA_MAX_QUEUE_SIZE, String.valueOf(30)); + conf.set(QueryServices.CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED, String.valueOf(true)); Configuration copy = new Configuration(conf); copy.addResource(confToClone); return copy;