Skip to content

Commit fab69a7

Browse files
oleg-vlskalex-plekhanov
authored andcommitted
IGNITE-24385 Fixed performance drop introduced by SQL plan history system view - Fixes #11848.
Signed-off-by: Aleksey Plekhanov <plehanov.alex@gmail.com>
1 parent 7459e0d commit fab69a7

File tree

8 files changed

+152
-92
lines changed

8 files changed

+152
-92
lines changed

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -710,13 +710,15 @@ private ListFieldsQueryCursor<?> mapAndExecutePlan(
710710
);
711711
}
712712

713-
ctx.query().runningQueryManager().planHistoryTracker().addPlan(
714-
plan.textPlan(),
715-
qry.sql(),
716-
qry.context().schemaName(),
717-
qry.context().isLocal(),
718-
CalciteQueryEngineConfiguration.ENGINE_NAME
719-
);
713+
if (ctx.query().runningQueryManager().planHistoryTracker().enabled()) {
714+
ctx.query().runningQueryManager().planHistoryTracker().addPlan(
715+
plan.textPlan(),
716+
qry.sql(),
717+
qry.context().schemaName(),
718+
qry.context().isLocal(),
719+
CalciteQueryEngineConfiguration.ENGINE_NAME
720+
);
721+
}
720722

721723
QueryProperties qryProps = qry.context().unwrap(QueryProperties.class);
722724

modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SqlPlanHistoryIntegrationTest.java

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -269,15 +269,17 @@ public void testSqlFieldsCrossCacheQueryFailed() throws Exception {
269269
*/
270270
@Test
271271
public void testSqlFieldsQueryWithReducePhase() throws Exception {
272-
runQueryWithReducePhase(() -> {
273-
try {
274-
startGridsMultiThreaded(1, 2);
272+
assumeTrue("Map/reduce queries are only applicable to H2 engine",
273+
sqlEngine == IndexingQueryEngineConfiguration.ENGINE_NAME);
275274

276-
awaitPartitionMapExchange();
277-
}
278-
catch (Exception e) {
279-
throw new RuntimeException(e);
280-
}
275+
assumeFalse("Only distributed queries have map and reduce phases", loc);
276+
277+
startTestGrid();
278+
279+
try {
280+
startGridsMultiThreaded(1, 2);
281+
282+
awaitPartitionMapExchange();
281283

282284
cacheQuery(new SqlFieldsQuery(SQL_WITH_REDUCE_PHASE).setDistributedJoins(true), "pers");
283285

@@ -286,11 +288,14 @@ public void testSqlFieldsQueryWithReducePhase() throws Exception {
286288
for (int i = 1; i <= 2; i++) {
287289
List<SqlPlanHistoryView> sqlPlansOnMapNode = getSqlPlanHistory(grid(i));
288290

289-
assertNotNull(sqlPlansOnMapNode);
291+
assertTrue(waitForCondition(() -> sqlPlansOnMapNode.size() == 2, 1000));
290292

291-
checkMetrics(2, sqlPlansOnMapNode);
293+
checkMetrics(sqlPlansOnMapNode);
292294
}
293-
});
295+
}
296+
catch (Exception e) {
297+
throw new RuntimeException(e);
298+
}
294299
}
295300

296301
/** Checks successful SqlQuery. */
@@ -356,7 +361,8 @@ public void testPlanHistoryEviction() throws Exception {
356361
cacheQuery(qry, "A");
357362
}
358363

359-
assertTrue(waitForCondition(() -> getSqlPlanHistory().size() == planHistorySize, 1000));
364+
assertTrue(waitForCondition(() -> getSqlPlanHistory().stream().map(SqlPlanHistoryView::sql).anyMatch(str ->
365+
str.contains("STR" + String.format("%02d", planHistorySize + PLAN_HISTORY_EXCESS))), 1000));
360366

361367
Set<String> qrys = getSqlPlanHistory().stream().map(SqlPlanHistoryView::sql).collect(Collectors.toSet());
362368

@@ -377,23 +383,21 @@ public void testPlanHistoryEviction() throws Exception {
377383
public void testEntryReplacement() throws Exception {
378384
startTestGrid();
379385

380-
long[] timeStamps = new long[2];
386+
long firstTs;
381387

382-
for (int i = 0; i < 2; i++) {
383-
cacheQuery(new SqlFieldsQuery(SQL), "A");
388+
cacheQuery(new SqlFieldsQuery(SQL), "A");
384389

385-
checkSqlPlanHistory(1);
390+
assertTrue(waitForCondition(() -> !getSqlPlanHistory().isEmpty(), 1000));
386391

387-
timeStamps[i] = F.first(getSqlPlanHistory()).lastStartTime().getTime();
392+
firstTs = F.first(getSqlPlanHistory()).lastStartTime().getTime();
388393

389-
if (i == 0) {
390-
long ts0 = U.currentTimeMillis();
394+
long curTs = U.currentTimeMillis();
391395

392-
assertTrue(waitForCondition(() -> (U.currentTimeMillis() != ts0), 1000));
393-
}
394-
}
396+
assertTrue(waitForCondition(() -> (U.currentTimeMillis() != curTs), 1000));
395397

396-
assertTrue(timeStamps[1] > timeStamps[0]);
398+
cacheQuery(new SqlFieldsQuery(SQL), "A");
399+
400+
assertTrue(waitForCondition(() -> F.first(getSqlPlanHistory()).lastStartTime().getTime() > firstTs, 1000));
397401
}
398402

399403
/** Checks that SQL plan history stays empty if the grid is started with a zero history size. */
@@ -489,21 +493,7 @@ public void runQueryWithoutPlan(Query qry) throws Exception {
489493

490494
cacheQuery(qry, "A");
491495

492-
checkSqlPlanHistory(0);
493-
}
494-
495-
/**
496-
* @param task Test task to execute.
497-
*/
498-
public void runQueryWithReducePhase(Runnable task) throws Exception {
499-
assumeTrue("Map/reduce queries are only applicable to H2 engine",
500-
sqlEngine == IndexingQueryEngineConfiguration.ENGINE_NAME);
501-
502-
assumeFalse("Only distributed queries have map and reduce phases", loc);
503-
504-
startTestGrid();
505-
506-
task.run();
496+
assertTrue(getSqlPlanHistory().isEmpty());
507497
}
508498

509499
/**
@@ -619,12 +609,10 @@ public List<SqlPlanHistoryView> getSqlPlanHistory(IgniteEx node) {
619609
*
620610
* @param size Number of SQL plan entries expected to be in the history.
621611
*/
622-
public void checkSqlPlanHistory(int size) {
623-
List<SqlPlanHistoryView> sqlPlans = getSqlPlanHistory();
624-
625-
assertNotNull(sqlPlans);
612+
public void checkSqlPlanHistory(int size) throws Exception {
613+
assertTrue(waitForCondition(() -> getSqlPlanHistory().size() == size, 1000));
626614

627-
checkMetrics(size, sqlPlans);
615+
checkMetrics(getSqlPlanHistory());
628616
}
629617

630618
/**
@@ -649,21 +637,17 @@ public void checkSqlPlanHistoryDml(int size, boolean isSimpleQry) {
649637
sqlPlans = sqlPlans.stream().filter(e -> e.plan().contains(check)).collect(Collectors.toList());
650638
}
651639

652-
checkMetrics(size, sqlPlans);
640+
assertTrue(sqlPlans.size() == size);
641+
642+
checkMetrics(sqlPlans);
653643
}
654644

655645
/**
656646
* Checks metrics of provided SQL plan history entries.
657647
*
658-
* @param size Number of SQL plan entries expected to be in the history.
659648
* @param sqlPlans Sql plans recorded in the history.
660649
*/
661-
public void checkMetrics(int size, List<SqlPlanHistoryView> sqlPlans) {
662-
assertTrue(size == sqlPlans.size());
663-
664-
if (size == 0)
665-
return;
666-
650+
public void checkMetrics(List<SqlPlanHistoryView> sqlPlans) {
667651
for (SqlPlanHistoryView plan : sqlPlans) {
668652
assertEquals(loc, plan.local());
669653
assertEquals(sqlEngine, plan.engine());

modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlan.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class SqlPlan {
4545
* @param qry Query.
4646
* @param schema Schema name.
4747
* @param loc Local query flag.
48+
* @param engine SQL engine.
4849
*/
4950
public SqlPlan(
5051
String plan,

modules/core/src/main/java/org/apache/ignite/internal/processors/query/running/SqlPlanHistoryTracker.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

2525
/** Class that manages recording and storing SQL plans. */
2626
public class SqlPlanHistoryTracker {
27+
/** Empty map. */
28+
private static final Map<SqlPlan, Long> EMPTY_MAP = Collections.emptyMap();
29+
2730
/** SQL plan history. */
2831
private Map<SqlPlan, Long> sqlPlanHistory;
2932

@@ -42,9 +45,7 @@ public SqlPlanHistoryTracker(int histSize) {
4245
* @param engine SQL engine.
4346
*/
4447
public void addPlan(String plan, String qry, String schema, boolean loc, String engine) {
45-
Map<SqlPlan, Long> emptyMap = Collections.emptyMap();
46-
47-
if (sqlPlanHistory == emptyMap)
48+
if (sqlPlanHistory == EMPTY_MAP)
4849
return;
4950

5051
SqlPlan sqlPlan = new SqlPlan(plan, qry, schema, loc, engine);
@@ -57,6 +58,11 @@ public Map<SqlPlan, Long> sqlPlanHistory() {
5758
return Collections.unmodifiableMap(sqlPlanHistory);
5859
}
5960

61+
/** */
62+
public boolean enabled() {
63+
return sqlPlanHistory != EMPTY_MAP;
64+
}
65+
6066
/**
6167
* @param histSize History size.
6268
*/

modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2QueryInfo.java

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ public class H2QueryInfo implements TrackableQuery {
7474
/** Query id. */
7575
private final long queryId;
7676

77+
/** Query SQL plan. */
78+
private volatile String plan;
79+
7780
/**
7881
* @param type Query type.
7982
* @param stmt Query statement.
@@ -117,8 +120,21 @@ public long queryId() {
117120
}
118121

119122
/** */
120-
public String plan() {
121-
return stmt.getPlanSQL();
123+
public synchronized String plan() {
124+
if (plan == null)
125+
plan = planWithoutScanCount(stmt.getPlanSQL());
126+
127+
return plan;
128+
}
129+
130+
/** */
131+
public String schema() {
132+
return schema;
133+
}
134+
135+
/** */
136+
public String sql() {
137+
return sql;
122138
}
123139

124140
/** */
@@ -179,7 +195,7 @@ public synchronized void resumeTracking() {
179195
.append(", lazy=").append(lazy)
180196
.append(", schema=").append(schema)
181197
.append(", sql='").append(sql)
182-
.append("', plan=").append(stmt.getPlanSQL());
198+
.append("', plan=").append(plan());
183199

184200
printInfo(msgSb);
185201

@@ -193,6 +209,43 @@ public boolean isSuspended() {
193209
return isSuspended;
194210
}
195211

212+
/**
213+
* If the same SQL query is executed sequentially within a single instance of {@link H2Connection} (which happens,
214+
* for example, during consecutive local query executions), the next execution plan is generated using
215+
* a {@link PreparedStatement} stored in the statement cache of this connection — H2Connection#statementCache.<br>
216+
* <br>
217+
* During the preparation of a PreparedStatement, a TableFilter object is created, where the variable scanCount
218+
* stores the number of elements scanned within the query. If this value is not zero, the generated execution plan
219+
* will contain a substring in the following format: "scanCount: X", where X is the value of the scanCount
220+
* variable at the time of plan generation.<br>
221+
* <br>
222+
* The scanCount variable is reset in the TableFilter#startQuery method. However, since execution plans are
223+
* generated and recorded asynchronously, there is no guarantee that plan generation happens immediately after
224+
* TableFilter#startQuery is called.<br>
225+
* <br>
226+
* As a result, identical execution plans differing only by the scanCount suffix may be recorded in the SQL plan
227+
* history. To prevent this, the suffix should be removed from the plan as soon as it is generated with the
228+
* Prepared#getPlanSQL method.<br>
229+
* <br>
230+
*
231+
* @param plan SQL plan.
232+
*
233+
* @return SQL plan without the scanCount suffix.
234+
*/
235+
public String planWithoutScanCount(String plan) {
236+
String res = null;
237+
238+
int start = plan.indexOf("\n /* scanCount");
239+
240+
if (start != -1) {
241+
int end = plan.indexOf("*/", start);
242+
243+
res = plan.substring(0, start) + plan.substring(end + 2);
244+
}
245+
246+
return (res == null) ? plan : res;
247+
}
248+
196249
/**
197250
* Query type.
198251
*/

modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,17 @@ private GridQueryFieldsResult executeSelectLocal(
469469
qryInfo
470470
);
471471

472-
runningQueryManager().planHistoryTracker().addPlan(
473-
qryInfo.plan(),
474-
qry,
475-
qryDesc.schemaName(),
476-
true,
477-
IndexingQueryEngineConfiguration.ENGINE_NAME
478-
);
472+
if (runningQueryManager().planHistoryTracker().enabled()) {
473+
H2QueryInfo qryInfo0 = qryInfo;
474+
475+
ctx.pools().getSystemExecutorService().submit(() ->
476+
runningQueryManager().planHistoryTracker().addPlan(
477+
qryInfo0.plan(),
478+
qryInfo0.sql(),
479+
qryInfo0.schema(),
480+
true,
481+
IndexingQueryEngineConfiguration.ENGINE_NAME));
482+
}
479483

480484
return new H2FieldsIterator(
481485
rs,
@@ -2300,13 +2304,15 @@ else if (plan.hasRows()) {
23002304
}
23012305
}
23022306
finally {
2303-
runningQueryManager().planHistoryTracker().addPlan(
2304-
dmlPlanInfo.toString(),
2305-
qryDesc.sql(),
2306-
qryDesc.schemaName(),
2307-
loc,
2308-
IndexingQueryEngineConfiguration.ENGINE_NAME
2309-
);
2307+
if (runningQueryManager().planHistoryTracker().enabled()) {
2308+
runningQueryManager().planHistoryTracker().addPlan(
2309+
dmlPlanInfo.toString(),
2310+
qryDesc.sql(),
2311+
qryDesc.schemaName(),
2312+
loc,
2313+
IndexingQueryEngineConfiguration.ENGINE_NAME
2314+
);
2315+
}
23102316
}
23112317
}
23122318

modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -493,13 +493,17 @@ private void onQueryRequest0(
493493
qryInfo
494494
);
495495

496-
h2.runningQueryManager().planHistoryTracker().addPlan(
497-
qryInfo.plan(),
498-
sql,
499-
schemaName,
500-
false,
501-
IndexingQueryEngineConfiguration.ENGINE_NAME
502-
);
496+
if (h2.runningQueryManager().planHistoryTracker().enabled()) {
497+
MapH2QueryInfo qryInfo0 = qryInfo;
498+
499+
ctx.pools().getSystemExecutorService().submit(() ->
500+
h2.runningQueryManager().planHistoryTracker().addPlan(
501+
qryInfo0.plan(),
502+
qryInfo0.sql(),
503+
qryInfo0.schema(),
504+
false,
505+
IndexingQueryEngineConfiguration.ENGINE_NAME));
506+
}
503507

504508
if (evt) {
505509
ctx.event().record(new CacheQueryExecutedEvent<>(

0 commit comments

Comments
 (0)