From eaccc47f2f41c438757e49bfa8aa9da3d436025a Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 12 Apr 2024 22:18:58 +1200 Subject: [PATCH] Refactor move background Query plan collection into CQueryPlanManager --- .../java/io/ebean/meta/QueryPlanRequest.java | 38 +++++++---------- .../api/NoopQueryPlanManager.java | 5 +++ .../ebeaninternal/api/QueryPlanManager.java | 5 +++ .../server/core/DefaultMetaInfoManager.java | 9 ++-- .../server/core/DefaultServer.java | 33 ++------------- .../server/core/InternalConfiguration.java | 4 +- .../server/query/CQueryPlanManager.java | 41 ++++++++++++++++--- .../DefaultQueryPlanListener.java | 2 +- .../query/finder/TestCustomerFinder.java | 9 ++-- 9 files changed, 75 insertions(+), 71 deletions(-) rename ebean-core/src/main/java/io/ebeaninternal/server/{core => query}/DefaultQueryPlanListener.java (96%) diff --git a/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java b/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java index 421e0c1511..d401b41adb 100644 --- a/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java +++ b/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java @@ -3,13 +3,24 @@ /** * Request used to capture query plans. */ -public class QueryPlanRequest { +public final class QueryPlanRequest { private long since; - private int maxCount; + private final int maxCount; - private long maxTimeMillis; + private final long maxTimeMillis; + + /** + * Create with the max number of plans and max capture time. + * + * @param maxCount The maximum number of plans to capture + * @param maxTimeMillis The maximum time after which we stop capturing more plans + */ + public QueryPlanRequest(int maxCount, long maxTimeMillis) { + this.maxCount = maxCount; + this.maxTimeMillis = maxTimeMillis; + } /** * Return the epoch time in millis for minimum bind capture time. @@ -39,16 +50,6 @@ public int maxCount() { return maxCount; } - /** - * Set the maximum number of plans to capture. - *

- * Use this to limit how much query plan capturing is done as query - * plan capture is actual database load. - */ - public void maxCount(int maxCount) { - this.maxCount = maxCount; - } - /** * Return the maximum amount of time we want to use to capture plans. *

@@ -57,15 +58,4 @@ public void maxCount(int maxCount) { public long maxTimeMillis() { return maxTimeMillis; } - - /** - * Set the maximum amount of time we want to use to capture plans. - *

- * Query plan collection will stop once this time is exceeded. We use - * this to ensure the query plan capture does not use excessive amount - * of time - put too much load on the database. - */ - public void maxTimeMillis(long maxTimeMillis) { - this.maxTimeMillis = maxTimeMillis; - } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java b/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java index 9c6a54952e..f692cece8e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java @@ -8,6 +8,11 @@ final class NoopQueryPlanManager implements QueryPlanManager { + @Override + public void startPlanCapture() { + // do nothing + } + @Override public void setDefaultThreshold(long thresholdMicros) { // do nothing diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java b/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java index da7ce2b430..a2478425ad 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java @@ -12,6 +12,11 @@ public interface QueryPlanManager { QueryPlanManager NOOP = new NoopQueryPlanManager(); + /** + * Start background capture of query plans if enabled. + */ + void startPlanCapture(); + /** * Update the global default threshold used when new query plans are created. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java index b4a29697c3..928d10c6e3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultMetaInfoManager.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.core; import io.ebean.meta.*; +import io.ebeaninternal.api.QueryPlanManager; import java.util.List; import java.util.function.Function; @@ -11,11 +12,13 @@ final class DefaultMetaInfoManager implements MetaInfoManager { private final DefaultServer server; + private final QueryPlanManager queryPlanManager; private final Function naming; - DefaultMetaInfoManager(DefaultServer server, Function naming) { + DefaultMetaInfoManager(DefaultServer server, QueryPlanManager queryPlanManager) { this.server = server; - this.naming = naming; + this.naming = server.config().getMetricNaming(); + this.queryPlanManager = queryPlanManager; } @Override @@ -25,7 +28,7 @@ public List queryPlanInit(QueryPlanInit initRequest) { @Override public List queryPlanCollectNow(QueryPlanRequest request) { - return server.queryPlanCollectNow(request); + return queryPlanManager.collect(request); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 0c5a8b45f3..8252a51a04 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -56,7 +56,6 @@ import java.time.Clock; import java.util.*; import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Function; @@ -105,7 +104,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { private final EncryptKeyManager encryptKeyManager; private final SpiJsonContext jsonContext; private final DocumentStore documentStore; - private final MetaInfoManager metaInfoManager; + private final DefaultMetaInfoManager metaInfoManager; private final CurrentTenantProvider currentTenantProvider; private final SpiLogManager logManager; private final PersistenceContextScope defaultPersistenceContextScope; @@ -156,8 +155,8 @@ public DefaultServer(InternalConfiguration config, ServerCacheManager cache) { DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this); this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor()); this.documentStore = docStoreComponents.documentStore(); - this.queryPlanManager = config.initQueryPlanManager(transactionManager); - this.metaInfoManager = new DefaultMetaInfoManager(this, this.config.getMetricNaming()); + this.queryPlanManager = config.initQueryPlanManager(this, transactionManager); + this.metaInfoManager = new DefaultMetaInfoManager(this, queryPlanManager); this.serverPlugins = config.getPlugins(); this.ddlGenerator = config.initDdlGenerator(this); this.scriptRunner = new DScriptRunner(this); @@ -326,31 +325,7 @@ public void start() { migrationRunner.loadProperties(config.getProperties()); migrationRunner.run(config.getDataSource()); } - startQueryPlanCapture(); - } - - private void startQueryPlanCapture() { - if (config.isQueryPlanCapture()) { - long secs = config.getQueryPlanCapturePeriodSecs(); - if (secs > 10) { - log.log(INFO, "capture query plan enabled, every {0}secs", secs); - backgroundExecutor.scheduleWithFixedDelay(this::collectQueryPlans, secs, secs, TimeUnit.SECONDS); - } - } - } - - private void collectQueryPlans() { - QueryPlanRequest request = new QueryPlanRequest(); - request.maxCount(config.getQueryPlanCaptureMaxCount()); - request.maxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis()); - - // obtains query explain plans ... - List plans = metaInfoManager.queryPlanCollectNow(request); - QueryPlanListener listener = config.getQueryPlanListener(); - if (listener == null) { - listener = DefaultQueryPlanListener.INSTANT; - } - listener.process(new QueryPlanCapture(this, plans)); + queryPlanManager.startPlanCapture(); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java index 9e2dbbfdc6..14eca74723 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java @@ -575,12 +575,12 @@ private SpiCacheManager initCacheManager() { return new DefaultServerCacheManager(builder); } - public QueryPlanManager initQueryPlanManager(TransactionManager transactionManager) { + public QueryPlanManager initQueryPlanManager(DefaultServer server, TransactionManager transactionManager) { if (!config.isQueryPlanEnable()) { return QueryPlanManager.NOOP; } long threshold = config.getQueryPlanThresholdMicros(); - return new CQueryPlanManager(transactionManager, threshold, queryPlanLogger(databasePlatform.platform()), extraMetrics); + return new CQueryPlanManager(server, transactionManager, threshold, queryPlanLogger(databasePlatform.platform()), extraMetrics); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java index b3a4091d41..3355d9aab8 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java @@ -1,22 +1,27 @@ package io.ebeaninternal.server.query; +import io.ebean.config.QueryPlanCapture; +import io.ebean.config.QueryPlanListener; import io.ebean.meta.MetaQueryPlan; import io.ebean.meta.QueryPlanRequest; import io.ebean.metric.TimedMetric; import io.ebeaninternal.api.*; +import io.ebeaninternal.server.core.DefaultServer; import io.ebeaninternal.server.transaction.TransactionManager; import io.ebeaninternal.server.bind.capture.BindCapture; import java.sql.Connection; import java.sql.SQLException; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.*; import static java.lang.System.Logger.Level.ERROR; +import static java.lang.System.Logger.Level.INFO; import static java.util.Collections.emptyList; public final class CQueryPlanManager implements QueryPlanManager { + private static final System.Logger log = CoreLog.internal; private static final Object dummy = new Object(); private final ConcurrentHashMap plans = new ConcurrentHashMap<>(); @@ -24,14 +29,25 @@ public final class CQueryPlanManager implements QueryPlanManager { private final QueryPlanLogger planLogger; private final TimedMetric timeCollection; private final TimedMetric timeBindCapture; + private final DefaultServer server; + private final QueryPlanListener listener; + private final int maxCount; + private final long maxTimeMillis; private long defaultThreshold; - public CQueryPlanManager(TransactionManager transactionManager, long defaultThreshold, QueryPlanLogger planLogger, ExtraMetrics extraMetrics) { + public CQueryPlanManager(DefaultServer server, TransactionManager transactionManager, long defaultThreshold, QueryPlanLogger planLogger, ExtraMetrics extraMetrics) { + this.server = server; this.transactionManager = transactionManager; this.defaultThreshold = defaultThreshold; this.planLogger = planLogger; this.timeCollection = extraMetrics.planCollect(); this.timeBindCapture = extraMetrics.bindCapture(); + + final var config = server.config(); + this.maxCount = config.getQueryPlanCaptureMaxCount(); + this.maxTimeMillis = config.getQueryPlanCaptureMaxTimeMillis(); + final var planListener = config.getQueryPlanListener(); + this.listener = (planListener != null) ? planListener : DefaultQueryPlanListener.INSTANT; } @Override @@ -49,15 +65,28 @@ public void notifyBindCapture(CQueryBindCapture planBind, long startNanos) { timeBindCapture.addSinceNanos(startNanos); } + @Override + public void startPlanCapture() { + final var config = server.config(); + if (config.isQueryPlanCapture()) { + long secs = config.getQueryPlanCapturePeriodSecs(); + if (secs > 10) { + log.log(INFO, "capture query plan enabled, every {0}secs", secs); + server.backgroundExecutor().scheduleWithFixedDelay(this::collectQueryPlans, secs, secs, TimeUnit.SECONDS); + } + } + } + + private void collectQueryPlans() { + List plans = collect(new QueryPlanRequest(maxCount, maxTimeMillis)); + listener.process(new QueryPlanCapture(server, plans)); + } + @Override public List collect(QueryPlanRequest request) { if (plans.isEmpty()) { return emptyList(); } - return collectPlans(request); - } - - private List collectPlans(QueryPlanRequest request) { try (Connection connection = transactionManager.queryPlanConnection()) { CQueryPlanRequest req = new CQueryPlanRequest(connection, request, plans.keySet().iterator()); while (req.hasNext()) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultQueryPlanListener.java similarity index 96% rename from ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java rename to ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultQueryPlanListener.java index f38b9c8fab..cabba02fac 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultQueryPlanListener.java @@ -1,4 +1,4 @@ -package io.ebeaninternal.server.core; +package io.ebeaninternal.server.query; import io.avaje.applog.AppLog; import io.ebean.config.QueryPlanCapture; diff --git a/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java b/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java index 823327e3f5..21e9e75c62 100644 --- a/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java +++ b/ebean-test/src/test/java/org/tests/query/finder/TestCustomerFinder.java @@ -199,11 +199,8 @@ public void test_finders_queryPlans() { } // obtains db query plans ... - QueryPlanRequest request = new QueryPlanRequest(); - // collect max 1000 plans (use something more like 10) - request.maxCount(1_000); - // don't collect any more plans if used 10 secs - request.maxTimeMillis(10_000); + // collect max 10 plans, after 10 secs don't collect any more plans + var request = new QueryPlanRequest(10, 10_000); List plans0 = server().metaInfo().queryPlanCollectNow(request); assertThat(plans0).isNotEmpty(); @@ -214,7 +211,7 @@ public void test_finders_queryPlans() { System.out.println(plan); } - //DB.getBackgroundExecutor().scheduleWithFixedDelay(...) + // DB.backgroundExecutor().scheduleWithFixedDelay(...) } @Test