Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor move background Query plan collection into CQueryPlanManager #3390

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 14 additions & 24 deletions ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -39,16 +50,6 @@ public int maxCount() {
return maxCount;
}

/**
* Set the maximum number of plans to capture.
* <p>
* 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.
* <p>
Expand All @@ -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.
* <p>
* 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

final class NoopQueryPlanManager implements QueryPlanManager {

@Override
public void startPlanCapture() {
// do nothing
}

@Override
public void setDefaultThreshold(long thresholdMicros) {
// do nothing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -11,11 +12,13 @@
final class DefaultMetaInfoManager implements MetaInfoManager {

private final DefaultServer server;
private final QueryPlanManager queryPlanManager;
private final Function<String, String> naming;

DefaultMetaInfoManager(DefaultServer server, Function<String, String> naming) {
DefaultMetaInfoManager(DefaultServer server, QueryPlanManager queryPlanManager) {
this.server = server;
this.naming = naming;
this.naming = server.config().getMetricNaming();
this.queryPlanManager = queryPlanManager;
}

@Override
Expand All @@ -25,7 +28,7 @@ public List<MetaQueryPlan> queryPlanInit(QueryPlanInit initRequest) {

@Override
public List<MetaQueryPlan> queryPlanCollectNow(QueryPlanRequest request) {
return server.queryPlanCollectNow(request);
return queryPlanManager.collect(request);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<MetaQueryPlan> plans = metaInfoManager.queryPlanCollectNow(request);
QueryPlanListener listener = config.getQueryPlanListener();
if (listener == null) {
listener = DefaultQueryPlanListener.INSTANT;
}
listener.process(new QueryPlanCapture(this, plans));
queryPlanManager.startPlanCapture();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,53 @@
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<CQueryBindCapture, Object> plans = new ConcurrentHashMap<>();
private final TransactionManager transactionManager;
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
Expand All @@ -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<MetaQueryPlan> plans = collect(new QueryPlanRequest(maxCount, maxTimeMillis));
listener.process(new QueryPlanCapture(server, plans));
}

@Override
public List<MetaQueryPlan> collect(QueryPlanRequest request) {
if (plans.isEmpty()) {
return emptyList();
}
return collectPlans(request);
}

private List<MetaQueryPlan> collectPlans(QueryPlanRequest request) {
try (Connection connection = transactionManager.queryPlanConnection()) {
CQueryPlanRequest req = new CQueryPlanRequest(connection, request, plans.keySet().iterator());
while (req.hasNext()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.ebeaninternal.server.core;
package io.ebeaninternal.server.query;

import io.avaje.applog.AppLog;
import io.ebean.config.QueryPlanCapture;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MetaQueryPlan> plans0 = server().metaInfo().queryPlanCollectNow(request);
assertThat(plans0).isNotEmpty();

Expand All @@ -214,7 +211,7 @@ public void test_finders_queryPlans() {
System.out.println(plan);
}

//DB.getBackgroundExecutor().scheduleWithFixedDelay(...)
// DB.backgroundExecutor().scheduleWithFixedDelay(...)
}

@Test
Expand Down
Loading