#1922 - Performance - Too pessimistic synchronized block for lazy loading on reference bean or l2 cache bean

This commit is contained in:
rob bygrave
2020-01-30 18:48:39 +13:00
parent f1cf94365c
commit 2bb52a9581
12 changed files with 161 additions and 31 deletions
@@ -1,6 +1,7 @@
package io.ebean.bean;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.ValuePair;
import javax.persistence.EntityNotFoundException;
@@ -104,9 +105,6 @@ public final class EntityBeanIntercept implements Serializable {
/**
* Create a intercept with a given entity.
* <p>
* Refer to agent ProxyConstructor.
* </p>
*/
public EntityBeanIntercept(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
@@ -821,14 +819,14 @@ public final class EntityBeanIntercept implements Serializable {
synchronized (this) {
if (beanLoader == null) {
BeanLoader serverLoader = (BeanLoader) DB.byName(ebeanServerName);
if (serverLoader == null) {
throw new PersistenceException("Server [" + ebeanServerName + "] was not found?");
final Database database = DB.byName(ebeanServerName);
if (database == null) {
throw new PersistenceException("Database [" + ebeanServerName + "] was not found?");
}
// For stand alone reference bean or after deserialisation lazy load
// using the ebeanServer. Synchronise only on the bean.
loadBeanInternal(loadProperty, serverLoader);
loadBeanInternal(loadProperty, database.getPluginApi());
return;
}
}
@@ -0,0 +1,48 @@
package io.ebean.bean;
import io.ebean.Database;
/**
* BeanLoader used when single beans are loaded (which is usually not ideal / N+1).
*/
public abstract class SingleBeanLoader implements BeanLoader {
protected final Database database;
SingleBeanLoader(Database database) {
this.database = database;
}
@Override
public String getName() {
return database.getName();
}
/**
* Single bean lazy loaded when bean from L2 cache.
*/
public static class L2 extends SingleBeanLoader {
public L2(Database database) {
super(database);
}
@Override
public void loadBean(EntityBeanIntercept ebi) {
database.getPluginApi().loadBeanL2(ebi);
}
}
/**
* Single bean lazy loaded when a reference bean.
*/
public static class Ref extends SingleBeanLoader {
public Ref(Database database) {
super(database);
}
@Override
public void loadBean(EntityBeanIntercept ebi) {
database.getPluginApi().loadBeanRef(ebi);
}
}
}
+18 -1
View File
@@ -1,6 +1,8 @@
package io.ebean.plugin;
import io.ebean.EbeanServer;
import io.ebean.bean.BeanLoader;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
@@ -10,7 +12,7 @@ import java.util.List;
/**
* Extensions to Database API made available to plugins.
*/
public interface SpiServer extends EbeanServer {
public interface SpiServer extends EbeanServer, BeanLoader {
/**
* Return the serverConfig.
@@ -52,4 +54,19 @@ public interface SpiServer extends EbeanServer {
*/
DataSource getReadOnlyDataSource();
/**
* Invoke lazy loading on this single bean (reference bean).
*/
void loadBeanRef(EntityBeanIntercept ebi);
/**
* Invoke lazy loading on this single bean (L2 cache bean).
*/
void loadBeanL2(EntityBeanIntercept ebi);
/**
* Invoke lazy loading on this single bean when no BeanLoader is set.
* Typically due to serialisation or multiple stateless updates.
*/
void loadBean(EntityBeanIntercept ebi);
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.api;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebean.metric.CountMetric;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetric;
@@ -12,6 +13,9 @@ public class ExtraMetrics {
private final TimedMetric bindCapture;
private final TimedMetric planCollect;
private final CountMetric loadOneL2;
private final CountMetric loadOneRef;
private final CountMetric loadOneNoLoader;
/**
* Create the extra metrics.
@@ -20,6 +24,9 @@ public class ExtraMetrics {
final MetricFactory factory = MetricFactory.get();
this.bindCapture = factory.createTimedMetric(MetricType.ORM, "ebean.queryplan.bindcapture");
this.planCollect = factory.createTimedMetric(MetricType.ORM, "ebean.queryplan.collect");
this.loadOneL2 = factory.createCountMetric(MetricType.ORM, "loadone.l2");
this.loadOneRef = factory.createCountMetric(MetricType.ORM, "loadone.ref");
this.loadOneNoLoader = factory.createCountMetric(MetricType.ORM, "loadone.noloader");
}
/**
@@ -36,11 +43,38 @@ public class ExtraMetrics {
return planCollect;
}
/**
* Increment counter for lazy loading one bean from L2 cache.
* All good when lazy loading also hits L2 cache.
*/
public void incrementLoadOneL2() {
loadOneL2.increment();
}
/**
* Increment counter for lazy loading on reference bean.
* We ought to be able to avoid this by changing to a tuned query.
*/
public void incrementLoadOneRef() {
loadOneRef.increment();
}
/**
* Increment counter for lazy loading one bean due to no loader.
* Likely due to multiple stateless updates or serialisation.
*/
public void incrementLoadOneNoLoader() {
loadOneNoLoader.increment();
}
/**
* Collect the metrics.
*/
public void visitMetrics(MetricVisitor visitor) {
bindCapture.visit(visitor);
planCollect.visit(visitor);
loadOneL2.visit(visitor);
loadOneRef.visit(visitor);
loadOneNoLoader.visit(visitor);
}
}
@@ -31,7 +31,7 @@ import java.util.function.Predicate;
/**
* Service Provider extension to EbeanServer.
*/
public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader, BeanCollectionLoader {
public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollectionLoader {
/**
* Return the log manager.
@@ -580,6 +580,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void loadBean(EntityBeanIntercept ebi) {
beanLoader.loadBean(ebi);
extraMetrics.incrementLoadOneNoLoader();
}
@Override
public void loadBeanRef(EntityBeanIntercept ebi) {
beanLoader.loadBean(ebi);
extraMetrics.incrementLoadOneRef();
}
@Override
public void loadBeanL2(EntityBeanIntercept ebi) {
beanLoader.loadBean(ebi);
extraMetrics.incrementLoadOneL2();
}
@Override
@@ -10,6 +10,7 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.SingleBeanLoader;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.config.EncryptKey;
import io.ebean.config.ServerConfig;
@@ -2040,7 +2041,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
if (disableLazyLoad) {
ebi.setDisableLazyLoad(true);
} else {
ebi.setBeanLoader(ebeanServer);
ebi.setBeanLoader(refBeanLoader());
}
ebi.setReference(idPropertyIndex);
if (Boolean.TRUE == readOnly) {
@@ -2058,6 +2059,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
}
SingleBeanLoader refBeanLoader() {
return new SingleBeanLoader.Ref(ebeanServer);
}
SingleBeanLoader l2BeanLoader() {
return new SingleBeanLoader.L2(ebeanServer);
}
/**
* Create a non read only reference bean without checking cacheSharableBeans.
*/
@@ -2072,7 +2081,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
EntityBean eb = createEntityBean();
id = convertSetId(id, eb);
EntityBeanIntercept ebi = eb._ebean_getIntercept();
ebi.setBeanLoader(ebeanServer);
ebi.setBeanLoader(refBeanLoader());
ebi.setReference(idPropertyIndex);
if (pc != null) {
contextPut(pc, id, eb);
@@ -690,7 +690,7 @@ final class BeanDescriptorCacheHelp<T> {
EntityBeanIntercept ebi = bean._ebean_getIntercept();
// Not using loadContext here so no batch lazy loading for these beans
ebi.setBeanLoader(desc.getEbeanServer());
ebi.setBeanLoader(desc.l2BeanLoader());
if (Boolean.TRUE.equals(readOnly)) {
ebi.setReadOnly(true);
}