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

alexpl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new fab69a78109 IGNITE-24385 Fixed performance drop introduced by SQL plan 
history system view - Fixes #11848.
fab69a78109 is described below

commit fab69a781090291d7b08a02f29356959171a6a8e
Author: oleg-vlsk <[email protected]>
AuthorDate: Fri Feb 7 23:43:15 2025 +0300

    IGNITE-24385 Fixed performance drop introduced by SQL plan history system 
view - Fixes #11848.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 .../query/calcite/exec/ExecutionServiceImpl.java   | 16 ++--
 .../integration/SqlPlanHistoryIntegrationTest.java | 86 +++++++++-------------
 .../internal/processors/query/running/SqlPlan.java |  1 +
 .../query/running/SqlPlanHistoryTracker.java       | 12 ++-
 .../internal/processors/query/h2/H2QueryInfo.java  | 59 ++++++++++++++-
 .../processors/query/h2/IgniteH2Indexing.java      | 34 +++++----
 .../query/h2/twostep/GridMapQueryExecutor.java     | 18 +++--
 .../query/h2/twostep/GridReduceQueryExecutor.java  | 18 +++--
 8 files changed, 152 insertions(+), 92 deletions(-)

diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
index c985dd6f4ae..4c4f8ff323e 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java
@@ -710,13 +710,15 @@ public class ExecutionServiceImpl<Row> extends 
AbstractService implements Execut
             );
         }
 
-        ctx.query().runningQueryManager().planHistoryTracker().addPlan(
-            plan.textPlan(),
-            qry.sql(),
-            qry.context().schemaName(),
-            qry.context().isLocal(),
-            CalciteQueryEngineConfiguration.ENGINE_NAME
-        );
+        if (ctx.query().runningQueryManager().planHistoryTracker().enabled()) {
+            ctx.query().runningQueryManager().planHistoryTracker().addPlan(
+                plan.textPlan(),
+                qry.sql(),
+                qry.context().schemaName(),
+                qry.context().isLocal(),
+                CalciteQueryEngineConfiguration.ENGINE_NAME
+            );
+        }
 
         QueryProperties qryProps = qry.context().unwrap(QueryProperties.class);
 
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SqlPlanHistoryIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SqlPlanHistoryIntegrationTest.java
index f649b849ec9..e82dafa9b89 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SqlPlanHistoryIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SqlPlanHistoryIntegrationTest.java
@@ -269,15 +269,17 @@ public class SqlPlanHistoryIntegrationTest extends 
GridCommonAbstractTest {
      */
     @Test
     public void testSqlFieldsQueryWithReducePhase() throws Exception {
-        runQueryWithReducePhase(() -> {
-            try {
-                startGridsMultiThreaded(1, 2);
+        assumeTrue("Map/reduce queries are only applicable to H2 engine",
+            sqlEngine == IndexingQueryEngineConfiguration.ENGINE_NAME);
 
-                awaitPartitionMapExchange();
-            }
-            catch (Exception e) {
-                throw new RuntimeException(e);
-            }
+        assumeFalse("Only distributed queries have map and reduce phases", 
loc);
+
+        startTestGrid();
+
+        try {
+            startGridsMultiThreaded(1, 2);
+
+            awaitPartitionMapExchange();
 
             cacheQuery(new 
SqlFieldsQuery(SQL_WITH_REDUCE_PHASE).setDistributedJoins(true), "pers");
 
@@ -286,11 +288,14 @@ public class SqlPlanHistoryIntegrationTest extends 
GridCommonAbstractTest {
             for (int i = 1; i <= 2; i++) {
                 List<SqlPlanHistoryView> sqlPlansOnMapNode = 
getSqlPlanHistory(grid(i));
 
-                assertNotNull(sqlPlansOnMapNode);
+                assertTrue(waitForCondition(() -> sqlPlansOnMapNode.size() == 
2, 1000));
 
-                checkMetrics(2, sqlPlansOnMapNode);
+                checkMetrics(sqlPlansOnMapNode);
             }
-        });
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /** Checks successful SqlQuery. */
@@ -356,7 +361,8 @@ public class SqlPlanHistoryIntegrationTest extends 
GridCommonAbstractTest {
             cacheQuery(qry, "A");
         }
 
-        assertTrue(waitForCondition(() -> getSqlPlanHistory().size() == 
planHistorySize, 1000));
+        assertTrue(waitForCondition(() -> 
getSqlPlanHistory().stream().map(SqlPlanHistoryView::sql).anyMatch(str ->
+            str.contains("STR" + String.format("%02d", planHistorySize + 
PLAN_HISTORY_EXCESS))), 1000));
 
         Set<String> qrys = 
getSqlPlanHistory().stream().map(SqlPlanHistoryView::sql).collect(Collectors.toSet());
 
@@ -377,23 +383,21 @@ public class SqlPlanHistoryIntegrationTest extends 
GridCommonAbstractTest {
     public void testEntryReplacement() throws Exception {
         startTestGrid();
 
-        long[] timeStamps = new long[2];
+        long firstTs;
 
-        for (int i = 0; i < 2; i++) {
-            cacheQuery(new SqlFieldsQuery(SQL), "A");
+        cacheQuery(new SqlFieldsQuery(SQL), "A");
 
-            checkSqlPlanHistory(1);
+        assertTrue(waitForCondition(() -> !getSqlPlanHistory().isEmpty(), 
1000));
 
-            timeStamps[i] = 
F.first(getSqlPlanHistory()).lastStartTime().getTime();
+        firstTs = F.first(getSqlPlanHistory()).lastStartTime().getTime();
 
-            if (i == 0) {
-                long ts0 = U.currentTimeMillis();
+        long curTs = U.currentTimeMillis();
 
-                assertTrue(waitForCondition(() -> (U.currentTimeMillis() != 
ts0), 1000));
-            }
-        }
+        assertTrue(waitForCondition(() -> (U.currentTimeMillis() != curTs), 
1000));
 
-        assertTrue(timeStamps[1] > timeStamps[0]);
+        cacheQuery(new SqlFieldsQuery(SQL), "A");
+
+        assertTrue(waitForCondition(() -> 
F.first(getSqlPlanHistory()).lastStartTime().getTime() > firstTs, 1000));
     }
 
     /** Checks that SQL plan history stays empty if the grid is started with a 
zero history size. */
@@ -489,21 +493,7 @@ public class SqlPlanHistoryIntegrationTest extends 
GridCommonAbstractTest {
 
         cacheQuery(qry, "A");
 
-        checkSqlPlanHistory(0);
-    }
-
-    /**
-     * @param task Test task to execute.
-     */
-    public void runQueryWithReducePhase(Runnable task) throws Exception {
-        assumeTrue("Map/reduce queries are only applicable to H2 engine",
-            sqlEngine == IndexingQueryEngineConfiguration.ENGINE_NAME);
-
-        assumeFalse("Only distributed queries have map and reduce phases", 
loc);
-
-        startTestGrid();
-
-        task.run();
+        assertTrue(getSqlPlanHistory().isEmpty());
     }
 
     /**
@@ -619,12 +609,10 @@ public class SqlPlanHistoryIntegrationTest extends 
GridCommonAbstractTest {
      *
      * @param size Number of SQL plan entries expected to be in the history.
      */
-    public void checkSqlPlanHistory(int size) {
-        List<SqlPlanHistoryView> sqlPlans = getSqlPlanHistory();
-
-        assertNotNull(sqlPlans);
+    public void checkSqlPlanHistory(int size) throws Exception {
+        assertTrue(waitForCondition(() -> getSqlPlanHistory().size() == size, 
1000));
 
-        checkMetrics(size, sqlPlans);
+        checkMetrics(getSqlPlanHistory());
     }
 
     /**
@@ -649,21 +637,17 @@ public class SqlPlanHistoryIntegrationTest extends 
GridCommonAbstractTest {
             sqlPlans = sqlPlans.stream().filter(e -> 
e.plan().contains(check)).collect(Collectors.toList());
         }
 
-        checkMetrics(size, sqlPlans);
+        assertTrue(sqlPlans.size() == size);
+
+        checkMetrics(sqlPlans);
     }
 
     /**
      * Checks metrics of provided SQL plan history entries.
      *
-     * @param size Number of SQL plan entries expected to be in the history.
      * @param sqlPlans Sql plans recorded in the history.
      */
-    public void checkMetrics(int size, List<SqlPlanHistoryView> sqlPlans) {
-        assertTrue(size == sqlPlans.size());
-
-        if (size == 0)
-            return;
-
+    public void checkMetrics(List<SqlPlanHistoryView> sqlPlans) {
         for (SqlPlanHistoryView plan : sqlPlans) {
             assertEquals(loc, plan.local());
             assertEquals(sqlEngine, plan.engine());
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlan.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlan.java
index 5e1876daafa..14bdefb6e3f 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlan.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlan.java
@@ -45,6 +45,7 @@ public class SqlPlan {
      * @param qry Query.
      * @param schema Schema name.
      * @param loc Local query flag.
+     * @param engine SQL engine.
      */
     public SqlPlan(
         String plan,
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlanHistoryTracker.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlanHistoryTracker.java
index c65e671fd56..70a77804b7f 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlanHistoryTracker.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlanHistoryTracker.java
@@ -24,6 +24,9 @@ import org.apache.ignite.internal.util.typedef.internal.U;
 
 /** Class that manages recording and storing SQL plans. */
 public class SqlPlanHistoryTracker {
+    /** Empty map. */
+    private static final Map<SqlPlan, Long> EMPTY_MAP = Collections.emptyMap();
+
     /** SQL plan history. */
     private Map<SqlPlan, Long> sqlPlanHistory;
 
@@ -42,9 +45,7 @@ public class SqlPlanHistoryTracker {
      * @param engine SQL engine.
      */
     public void addPlan(String plan, String qry, String schema, boolean loc, 
String engine) {
-        Map<SqlPlan, Long> emptyMap = Collections.emptyMap();
-
-        if (sqlPlanHistory == emptyMap)
+        if (sqlPlanHistory == EMPTY_MAP)
             return;
 
         SqlPlan sqlPlan = new SqlPlan(plan, qry, schema, loc, engine);
@@ -57,6 +58,11 @@ public class SqlPlanHistoryTracker {
         return Collections.unmodifiableMap(sqlPlanHistory);
     }
 
+    /** */
+    public boolean enabled() {
+        return sqlPlanHistory != EMPTY_MAP;
+    }
+
     /**
      * @param histSize History size.
      */
diff --git 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2QueryInfo.java
 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2QueryInfo.java
index 95ce6c1bf7e..0bccf8134ba 100644
--- 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2QueryInfo.java
+++ 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2QueryInfo.java
@@ -74,6 +74,9 @@ public class H2QueryInfo implements TrackableQuery {
     /** Query id. */
     private final long queryId;
 
+    /** Query SQL plan. */
+    private volatile String plan;
+
     /**
      * @param type Query type.
      * @param stmt Query statement.
@@ -117,8 +120,21 @@ public class H2QueryInfo implements TrackableQuery {
     }
 
     /** */
-    public String plan() {
-        return stmt.getPlanSQL();
+    public synchronized String plan() {
+        if (plan == null)
+            plan = planWithoutScanCount(stmt.getPlanSQL());
+
+        return plan;
+    }
+
+    /** */
+    public String schema() {
+        return schema;
+    }
+
+    /** */
+    public String sql() {
+        return sql;
     }
 
     /** */
@@ -179,7 +195,7 @@ public class H2QueryInfo implements TrackableQuery {
                 .append(", lazy=").append(lazy)
                 .append(", schema=").append(schema)
                 .append(", sql='").append(sql)
-                .append("', plan=").append(stmt.getPlanSQL());
+                .append("', plan=").append(plan());
 
         printInfo(msgSb);
 
@@ -193,6 +209,43 @@ public class H2QueryInfo implements TrackableQuery {
         return isSuspended;
     }
 
+    /**
+     * If the same SQL query is executed sequentially within a single instance 
of {@link H2Connection} (which happens,
+     * for example, during consecutive local query executions), the next 
execution plan is generated using
+     * a {@link PreparedStatement} stored in the statement cache of this 
connection — H2Connection#statementCache.<br>
+     * <br>
+     * During the preparation of a PreparedStatement, a TableFilter object is 
created, where the variable scanCount
+     * stores the number of elements scanned within the query. If this value 
is not zero, the generated execution plan
+     * will contain a substring in the following format: "scanCount: X", where 
X is the value of the scanCount
+     * variable at the time of plan generation.<br>
+     * <br>
+     * The scanCount variable is reset in the TableFilter#startQuery method. 
However, since execution plans are
+     * generated and recorded asynchronously, there is no guarantee that plan 
generation happens immediately after
+     * TableFilter#startQuery is called.<br>
+     * <br>
+     * As a result, identical execution plans differing only by the scanCount 
suffix may be recorded in the SQL plan
+     * history. To prevent this, the suffix should be removed from the plan as 
soon as it is generated with the
+     * Prepared#getPlanSQL method.<br>
+     * <br>
+     *
+     * @param plan SQL plan.
+     *
+     * @return SQL plan without the scanCount suffix.
+     */
+    public String planWithoutScanCount(String plan) {
+        String res = null;
+
+        int start = plan.indexOf("\n    /* scanCount");
+
+        if (start != -1) {
+            int end = plan.indexOf("*/", start);
+
+            res = plan.substring(0, start) + plan.substring(end + 2);
+        }
+
+        return (res == null) ? plan : res;
+    }
+
     /**
      * Query type.
      */
diff --git 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
index 86ee2d79e8a..7e821d980e8 100644
--- 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
+++ 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
@@ -469,13 +469,17 @@ public class IgniteH2Indexing implements 
GridQueryIndexing {
                         qryInfo
                     );
 
-                    runningQueryManager().planHistoryTracker().addPlan(
-                        qryInfo.plan(),
-                        qry,
-                        qryDesc.schemaName(),
-                        true,
-                        IndexingQueryEngineConfiguration.ENGINE_NAME
-                    );
+                    if (runningQueryManager().planHistoryTracker().enabled()) {
+                        H2QueryInfo qryInfo0 = qryInfo;
+
+                        ctx.pools().getSystemExecutorService().submit(() ->
+                            runningQueryManager().planHistoryTracker().addPlan(
+                                qryInfo0.plan(),
+                                qryInfo0.sql(),
+                                qryInfo0.schema(),
+                                true,
+                                IndexingQueryEngineConfiguration.ENGINE_NAME));
+                    }
 
                     return new H2FieldsIterator(
                         rs,
@@ -2300,13 +2304,15 @@ public class IgniteH2Indexing implements 
GridQueryIndexing {
             }
         }
         finally {
-            runningQueryManager().planHistoryTracker().addPlan(
-                dmlPlanInfo.toString(),
-                qryDesc.sql(),
-                qryDesc.schemaName(),
-                loc,
-                IndexingQueryEngineConfiguration.ENGINE_NAME
-            );
+            if (runningQueryManager().planHistoryTracker().enabled()) {
+                runningQueryManager().planHistoryTracker().addPlan(
+                    dmlPlanInfo.toString(),
+                    qryDesc.sql(),
+                    qryDesc.schemaName(),
+                    loc,
+                    IndexingQueryEngineConfiguration.ENGINE_NAME
+                );
+            }
         }
     }
 
diff --git 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
index a3b5d3a9c90..751edc8f917 100644
--- 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
+++ 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java
@@ -493,13 +493,17 @@ public class GridMapQueryExecutor {
                             qryInfo
                         );
 
-                        h2.runningQueryManager().planHistoryTracker().addPlan(
-                            qryInfo.plan(),
-                            sql,
-                            schemaName,
-                            false,
-                            IndexingQueryEngineConfiguration.ENGINE_NAME
-                        );
+                        if 
(h2.runningQueryManager().planHistoryTracker().enabled()) {
+                            MapH2QueryInfo qryInfo0 = qryInfo;
+
+                            ctx.pools().getSystemExecutorService().submit(() ->
+                                
h2.runningQueryManager().planHistoryTracker().addPlan(
+                                    qryInfo0.plan(),
+                                    qryInfo0.sql(),
+                                    qryInfo0.schema(),
+                                    false,
+                                    
IndexingQueryEngineConfiguration.ENGINE_NAME));
+                        }
 
                         if (evt) {
                             ctx.event().record(new CacheQueryExecutedEvent<>(
diff --git 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java
 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java
index 66424b17df3..28c0fcf49ab 100644
--- 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java
+++ 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java
@@ -542,13 +542,17 @@ public class GridReduceQueryExecutor {
                             qryInfo
                         );
 
-                        h2.runningQueryManager().planHistoryTracker().addPlan(
-                            qryInfo.plan(),
-                            qry.originalSql(),
-                            schemaName,
-                            qry.isLocal(),
-                            IndexingQueryEngineConfiguration.ENGINE_NAME
-                        );
+                        if 
(h2.runningQueryManager().planHistoryTracker().enabled()) {
+                            ReduceH2QueryInfo qryInfo0 = qryInfo;
+
+                            ctx.pools().getSystemExecutorService().submit(() ->
+                                
h2.runningQueryManager().planHistoryTracker().addPlan(
+                                    qryInfo0.plan(),
+                                    qryInfo0.sql(),
+                                    qryInfo0.schema(),
+                                    qry.isLocal(),
+                                    
IndexingQueryEngineConfiguration.ENGINE_NAME));
+                        }
 
                         resIter = new H2FieldsIterator(
                             res,

Reply via email to