This is an automated email from the ASF dual-hosted git repository.
jooger pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 97ac8dcfa78 IGNITE-26155 Expire of cache of SQL plans (#6467)
97ac8dcfa78 is described below
commit 97ac8dcfa78a54f5ee46882beb42d6d2bfb2399b
Author: Max Zhuravkov <[email protected]>
AuthorDate: Wed Aug 27 11:15:06 2025 +0300
IGNITE-26155 Expire of cache of SQL plans (#6467)
---
.../configuration/ignite-snapshot.bin | Bin 5503 -> 5511 bytes
.../SqlPlannerDistributedConfigurationSchema.java | 7 +++
.../sql/engine/prepare/PrepareServiceImpl.java | 5 +-
.../sql/engine/util/cache/CacheFactory.java | 14 ++++++
.../engine/util/cache/CaffeineCacheFactory.java | 10 ++++
.../sql/engine/exec/ExecutionServiceImplTest.java | 9 ++++
.../sql/engine/framework/TestBuilders.java | 2 +
.../sql/engine/planner/PlannerTimeoutTest.java | 13 ++++-
.../sql/engine/prepare/PrepareServiceImplTest.java | 54 ++++++++++++++++++++-
.../sql/engine/util/EmptyCacheFactory.java | 6 +++
.../sql/metrics/PlanningCacheMetricsTest.java | 2 +-
11 files changed, 117 insertions(+), 5 deletions(-)
diff --git
a/modules/runner/src/test/resources/compatibility/configuration/ignite-snapshot.bin
b/modules/runner/src/test/resources/compatibility/configuration/ignite-snapshot.bin
index 7e4e530f8d2..503adbf67df 100644
Binary files
a/modules/runner/src/test/resources/compatibility/configuration/ignite-snapshot.bin
and
b/modules/runner/src/test/resources/compatibility/configuration/ignite-snapshot.bin
differ
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/configuration/distributed/SqlPlannerDistributedConfigurationSchema.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/configuration/distributed/SqlPlannerDistributedConfigurationSchema.java
index 80d31c7a24e..b37ea213d23 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/configuration/distributed/SqlPlannerDistributedConfigurationSchema.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/configuration/distributed/SqlPlannerDistributedConfigurationSchema.java
@@ -38,4 +38,11 @@ public class SqlPlannerDistributedConfigurationSchema {
@Value(hasDefault = true)
@Range(min = 0)
public final int estimatedNumberOfQueries = 1024;
+
+ /**
+ * The number of seconds after which a query plan is removed from the
query plan cache if it is not used.
+ */
+ @Value(hasDefault = true)
+ @Range(min = 0)
+ public final int planCacheExpiresAfterSeconds = 30 * 60;
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
index 3d2c3ae4622..16a7cfcce28 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
@@ -26,6 +26,7 @@ import static
org.apache.ignite.internal.sql.engine.util.Commons.fastQueryOptimi
import static
org.apache.ignite.internal.thread.ThreadOperation.NOTHING_ALLOWED;
import static org.apache.ignite.lang.ErrorGroups.Sql.EXECUTION_CANCELLED_ERR;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -164,6 +165,7 @@ public class PrepareServiceImpl implements PrepareService {
cacheFactory,
ddlSqlToCommandConverter,
clusterCfg.planner().maxPlanningTimeMillis().value(),
+ clusterCfg.planner().planCacheExpiresAfterSeconds().value(),
nodeCfg.planner().threadCount().value(),
metricManager,
schemaManager
@@ -188,6 +190,7 @@ public class PrepareServiceImpl implements PrepareService {
DdlSqlToCommandConverter ddlConverter,
long plannerTimeout,
int plannerThreadCount,
+ int planExpirySeconds,
MetricManager metricManager,
SqlSchemaManager schemaManager
) {
@@ -199,7 +202,7 @@ public class PrepareServiceImpl implements PrepareService {
this.schemaManager = schemaManager;
sqlPlanCacheMetricSource = new SqlPlanCacheMetricSource();
- cache = cacheFactory.create(cacheSize, sqlPlanCacheMetricSource);
+ cache = cacheFactory.create(cacheSize, sqlPlanCacheMetricSource,
Duration.ofSeconds(planExpirySeconds));
}
/** {@inheritDoc} */
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/cache/CacheFactory.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/cache/CacheFactory.java
index 4d4c35df5bc..d1e28046b0a 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/cache/CacheFactory.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/cache/CacheFactory.java
@@ -17,6 +17,8 @@
package org.apache.ignite.internal.sql.engine.util.cache;
+import java.time.Duration;
+
/**
* Factory that creates a cache.
*/
@@ -41,4 +43,16 @@ public interface CacheFactory {
* @param <V> Type of the value object.
*/
<K, V> Cache<K, V> create(int size, StatsCounter statCounter);
+
+ /**
+ * Creates a cache of the required size, controls whether statistics
should be collected, and specifies entry expiration time.
+ *
+ * @param size Desired size of the cache.
+ * @param statCounter Cache statistic accumulator.
+ * @param expireAfterAccess Duration to use for expiration.
+ * @return An instance of the cache.
+ * @param <K> Type of the key object.
+ * @param <V> Type of the value object.
+ */
+ <K, V> Cache<K, V> create(int size, StatsCounter statCounter, Duration
expireAfterAccess);
}
diff --git
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/cache/CaffeineCacheFactory.java
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/cache/CaffeineCacheFactory.java
index 4b4c01824cb..55e9e8b0767 100644
---
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/cache/CaffeineCacheFactory.java
+++
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/cache/CaffeineCacheFactory.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.engine.util.cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
+import java.time.Duration;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -54,6 +55,11 @@ public class CaffeineCacheFactory implements CacheFactory {
/** {@inheritDoc} */
@Override
public <K, V> Cache<K, V> create(int size, StatsCounter statCounter) {
+ return create(size, statCounter, null);
+ }
+
+ @Override
+ public <K, V> Cache<K, V> create(int size, StatsCounter statCounter,
Duration expireAfterAccess) {
Caffeine<Object, Object> builder = Caffeine.newBuilder()
.maximumSize(size);
@@ -65,6 +71,10 @@ public class CaffeineCacheFactory implements CacheFactory {
builder.recordStats(() -> new
CaffeineStatsCounterAdapter(statCounter));
}
+ if (expireAfterAccess != null) {
+ builder.expireAfterAccess(expireAfterAccess);
+ }
+
return new CaffeineCacheToCacheAdapter<>(builder.build());
}
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
index 9b907e3ff18..6a23a9bace5 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
@@ -48,6 +48,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -187,6 +188,8 @@ public class ExecutionServiceImplTest extends
BaseIgniteAbstractTest {
public static final int PLANNING_THREAD_COUNT = 2;
+ public static final int PLAN_EXPIRATION_SECONDS = Integer.MAX_VALUE;
+
/** Timeout in ms for stopping execution service. */
private static final long SHUTDOWN_TIMEOUT = 5_000;
@@ -256,6 +259,7 @@ public class ExecutionServiceImplTest extends
BaseIgniteAbstractTest {
converter,
PLANNING_TIMEOUT,
PLANNING_THREAD_COUNT,
+ PLAN_EXPIRATION_SECONDS,
metricManager,
new PredefinedSchemaManager(schema)
);
@@ -1465,6 +1469,11 @@ public class ExecutionServiceImplTest extends
BaseIgniteAbstractTest {
throw new UnsupportedOperationException();
}
+ @Override
+ public <K, V> Cache<K, V> create(int size, StatsCounter statCounter,
Duration expireAfterAccess) {
+ throw new UnsupportedOperationException();
+ }
+
private static class BlockOnComputeCache<K, V> extends
EmptyCacheFactory.EmptyCache<K, V> {
private final CountDownLatch waitLatch;
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
index ed64c9f9720..85c5a6e50c5 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
@@ -21,6 +21,7 @@ import static java.util.UUID.randomUUID;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static
org.apache.ignite.internal.sql.engine.exec.ExecutionServiceImplTest.PLANNING_THREAD_COUNT;
+import static
org.apache.ignite.internal.sql.engine.exec.ExecutionServiceImplTest.PLAN_EXPIRATION_SECONDS;
import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
@@ -752,6 +753,7 @@ public class TestBuilders {
new DdlSqlToCommandConverter(storageProfiles ->
completedFuture(null)),
planningTimeout,
PLANNING_THREAD_COUNT,
+ PLAN_EXPIRATION_SECONDS,
new NoOpMetricManager(),
schemaManager
);
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PlannerTimeoutTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PlannerTimeoutTest.java
index 7b1713b8a86..94f20b76755 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PlannerTimeoutTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/PlannerTimeoutTest.java
@@ -67,8 +67,17 @@ public class PlannerTimeoutTest extends AbstractPlannerTest {
IgniteSchema schema = createSchema(createTestTable("T1"));
SqlOperationContext ctx = operationContext();
- PrepareService prepareService = new PrepareServiceImpl("test", 0,
- CaffeineCacheFactory.INSTANCE, null, plannerTimeout, 1, new
MetricManagerImpl(), new PredefinedSchemaManager(schema));
+ PrepareService prepareService = new PrepareServiceImpl(
+ "test",
+ 0,
+ CaffeineCacheFactory.INSTANCE,
+ null,
+ plannerTimeout,
+ 1,
+ Integer.MAX_VALUE,
+ new MetricManagerImpl(),
+ new PredefinedSchemaManager(schema)
+ );
prepareService.start();
try {
ParserService parserService = new ParserServiceImpl();
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImplTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImplTest.java
index f5875d3845a..b6a7b9038fc 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImplTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImplTest.java
@@ -34,12 +34,14 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import java.time.Duration;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.ignite.internal.hlc.HybridClockImpl;
@@ -69,6 +71,7 @@ import org.apache.ignite.lang.ErrorGroups.Sql;
import org.apache.ignite.sql.ColumnMetadata;
import org.apache.ignite.sql.ColumnType;
import org.apache.ignite.sql.SqlException;
+import org.awaitility.Awaitility;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -318,6 +321,11 @@ public class PrepareServiceImplTest extends
BaseIgniteAbstractTest {
public <K, V> Cache<K, V> create(int size, StatsCounter
statCounter) {
return (Cache<K, V>) cache;
}
+
+ @Override
+ public <K, V> Cache<K, V> create(int size, StatsCounter
statCounter, Duration expireAfterAccess) {
+ return (Cache<K, V>) cache;
+ }
};
PrepareServiceImpl service = createPlannerService(schema,
cacheFactory, 100);
@@ -367,6 +375,41 @@ public class PrepareServiceImplTest extends
BaseIgniteAbstractTest {
));
}
+ @Test
+ public void planCacheExpiry() {
+ IgniteTable table = TestBuilders.table()
+ .name("T")
+ .addColumn("C", NativeTypes.INT32)
+ .distribution(IgniteDistributions.single())
+ .build();
+
+ IgniteSchema schema = new IgniteSchema("TEST", 0, List.of(table));
+
+ Awaitility.await().timeout(30, TimeUnit.SECONDS).untilAsserted(() -> {
+ int expireSeconds = 2;
+ PrepareService service = createPlannerService(schema,
CaffeineCacheFactory.INSTANCE, Integer.MAX_VALUE, 2);
+
+ String query = "SELECT * FROM test.t WHERE c = 1";
+ QueryPlan p0 = await(service.prepareAsync(parse(query),
operationContext().build()));
+
+ // Expires if not used
+ TimeUnit.SECONDS.sleep(expireSeconds * 2);
+ QueryPlan p2 = await(service.prepareAsync(parse(query),
operationContext().build()));
+ assertNotSame(p0, p2);
+
+ // Returns the previous plan
+ TimeUnit.MILLISECONDS.sleep(500);
+
+ QueryPlan p3 = await(service.prepareAsync(parse(query),
operationContext().build()));
+ assertSame(p2, p3);
+
+ // Eventually expires
+ TimeUnit.SECONDS.sleep(expireSeconds * 2);
+ QueryPlan p4 = await(service.prepareAsync(parse(query),
operationContext().build()));
+ assertNotSame(p4, p3);
+ });
+ }
+
private static Stream<Arguments> parameterTypes() {
int noScale = ColumnMetadata.UNDEFINED_SCALE;
int noPrecision = ColumnMetadata.UNDEFINED_PRECISION;
@@ -429,8 +472,17 @@ public class PrepareServiceImplTest extends
BaseIgniteAbstractTest {
}
private static PrepareServiceImpl createPlannerService(IgniteSchema
schemas, CacheFactory cacheFactory, int timeoutMillis) {
+ return createPlannerService(schemas, cacheFactory, timeoutMillis,
Integer.MAX_VALUE);
+ }
+
+ private static PrepareServiceImpl createPlannerService(
+ IgniteSchema schemas,
+ CacheFactory cacheFactory,
+ int timeoutMillis,
+ int planExpireSeconds
+ ) {
PrepareServiceImpl service = new PrepareServiceImpl("test", 1000,
cacheFactory,
- mock(DdlSqlToCommandConverter.class), timeoutMillis, 2,
mock(MetricManagerImpl.class),
+ mock(DdlSqlToCommandConverter.class), timeoutMillis, 2,
planExpireSeconds, mock(MetricManagerImpl.class),
new PredefinedSchemaManager(schemas));
createdServices.add(service);
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/EmptyCacheFactory.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/EmptyCacheFactory.java
index afa7efdb535..b2e00a9f74f 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/EmptyCacheFactory.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/EmptyCacheFactory.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.sql.engine.util;
+import java.time.Duration;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -50,6 +51,11 @@ public class EmptyCacheFactory implements CacheFactory {
return create(size);
}
+ @Override
+ public <K, V> Cache<K, V> create(int size, StatsCounter statCounter,
Duration expireAfterAccess) {
+ return create(size);
+ }
+
/** A cache that keeps no object. */
public static class EmptyCache<K, V> implements Cache<K, V> {
@Override
diff --git
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/metrics/PlanningCacheMetricsTest.java
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/metrics/PlanningCacheMetricsTest.java
index ec92e77b180..5cb1bd0d4b2 100644
---
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/metrics/PlanningCacheMetricsTest.java
+++
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/metrics/PlanningCacheMetricsTest.java
@@ -62,7 +62,7 @@ public class PlanningCacheMetricsTest extends
AbstractPlannerTest {
IgniteSchema schema = createSchema(table);
PrepareService prepareService = new PrepareServiceImpl(
- "test", 2, cacheFactory, null, 15_000L, 2, metricManager, new
PredefinedSchemaManager(schema)
+ "test", 2, cacheFactory, null, 15_000L, 2, Integer.MAX_VALUE,
metricManager, new PredefinedSchemaManager(schema)
);
prepareService.start();