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 <shh...@gmail.com>
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();

Reply via email to