Skip to content

Commit

Permalink
PR ebean-orm#2956 - FEATURE: Tenant partitioned caches could improve …
Browse files Browse the repository at this point in the history
…cache-hit ratio
  • Loading branch information
rPraml committed Aug 10, 2023
1 parent 4ab43e0 commit a7db1f6
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 69 deletions.
26 changes: 26 additions & 0 deletions ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ public class DatabaseConfig {
private int backgroundExecutorShutdownSecs = 30;
private BackgroundExecutorWrapper backgroundExecutorWrapper = new MdcBackgroundExecutorWrapper();

private boolean tenantPartitionedCache;

// defaults for the L2 bean caching

private int cacheMaxSize = 10000;
Expand Down Expand Up @@ -1470,6 +1472,21 @@ public void setBackgroundExecutorWrapper(BackgroundExecutorWrapper backgroundExe
this.backgroundExecutorWrapper = backgroundExecutorWrapper;
}

/**
* Returns, if the caches are partitioned by tenant.
*/
public boolean isTenantPartitionedCache() {
return tenantPartitionedCache;
}

/**
* Sets the tenant partitioning mode for caches. This means, caches are created on demand,
* but they may not get invalidated across tenant boundaries *
*/
public void setTenantPartitionedCache(boolean tenantPartitionedCache) {
this.tenantPartitionedCache = tenantPartitionedCache;
}

/**
* Return the L2 cache default max size.
*/
Expand Down Expand Up @@ -2949,6 +2966,15 @@ protected void loadSettings(PropertiesWrapper p) {
ddlPlaceholders = p.get("ddl.placeholders", ddlPlaceholders);
ddlHeader = p.get("ddl.header", ddlHeader);

tenantPartitionedCache = p.getBoolean("tenantPartitionedCache", tenantPartitionedCache);

cacheMaxIdleTime = p.getInt("cacheMaxIdleTime", cacheMaxIdleTime);
cacheMaxSize = p.getInt("cacheMaxSize", cacheMaxSize);
cacheMaxTimeToLive = p.getInt("cacheMaxTimeToLive", cacheMaxTimeToLive);
queryCacheMaxIdleTime = p.getInt("queryCacheMaxIdleTime", queryCacheMaxIdleTime);
queryCacheMaxSize = p.getInt("queryCacheMaxSize", queryCacheMaxSize);
queryCacheMaxTimeToLive = p.getInt("queryCacheMaxTimeToLive", queryCacheMaxTimeToLive);

// read tenant-configuration from config:
// tenant.mode = NONE | DB | SCHEMA | CATALOG | PARTITION
String mode = p.get("tenant.mode");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,31 @@
public final class CacheManagerOptions {

private final ClusterManager clusterManager;
private final DatabaseConfig databaseConfig;
private final String serverName;
private final boolean localL2Caching;
private CurrentTenantProvider currentTenantProvider;
private QueryCacheEntryValidate queryCacheEntryValidate;
private ServerCacheFactory cacheFactory = new DefaultServerCacheFactory();
private ServerCacheOptions beanDefault = new ServerCacheOptions();
private ServerCacheOptions queryDefault = new ServerCacheOptions();
private final boolean tenantPartitionedCache;

CacheManagerOptions() {
this.localL2Caching = true;
this.clusterManager = null;
this.databaseConfig = null;
this.serverName = "db";
this.tenantPartitionedCache = false;
this.cacheFactory = new DefaultServerCacheFactory();
this.beanDefault = new ServerCacheOptions();
this.queryDefault = new ServerCacheOptions();
}

public CacheManagerOptions(ClusterManager clusterManager, DatabaseConfig config, boolean localL2Caching) {
this.clusterManager = clusterManager;
this.databaseConfig = config;
this.serverName = config.getName();
this.localL2Caching = localL2Caching;
this.currentTenantProvider = config.getCurrentTenantProvider();
this.tenantPartitionedCache = config.isTenantPartitionedCache();
}

public CacheManagerOptions with(ServerCacheOptions beanDefault, ServerCacheOptions queryDefault) {
Expand All @@ -55,7 +58,7 @@ public CacheManagerOptions with(CurrentTenantProvider currentTenantProvider) {
}

public String getServerName() {
return (databaseConfig == null) ? "db" : databaseConfig.getName();
return serverName;
}

public boolean isLocalL2Caching() {
Expand Down Expand Up @@ -85,4 +88,6 @@ public ClusterManager getClusterManager() {
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return queryCacheEntryValidate;
}

public boolean isTenantPartitionedCache() { return tenantPartitionedCache; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ final class DefaultCacheHolder {
private final ServerCacheOptions queryDefault;
private final CurrentTenantProvider tenantProvider;
private final QueryCacheEntryValidate queryCacheEntryValidate;
private final boolean tenantPartitionedCache;

DefaultCacheHolder(CacheManagerOptions builder) {
this.cacheFactory = builder.getCacheFactory();
this.beanDefault = builder.getBeanDefault();
this.queryDefault = builder.getQueryDefault();
this.tenantProvider = builder.getCurrentTenantProvider();
this.queryCacheEntryValidate = builder.getQueryCacheEntryValidate();
this.tenantPartitionedCache = builder.isTenantPartitionedCache();
}

void visitMetrics(MetricVisitor visitor) {
Expand All @@ -56,16 +58,42 @@ ServerCache getCache(Class<?> beanType, String collectionProperty) {
return getCacheInternal(beanType, ServerCacheType.COLLECTION_IDS, collectionProperty);
}

private String key(String beanName) {
if (tenantPartitionedCache) {
StringBuilder sb = new StringBuilder(beanName.length() + 64);
sb.append(beanName);
sb.append('.');
sb.append(tenantProvider.currentId());
return sb.toString();
} else {
return beanName;
}
}

private String key(String beanName, ServerCacheType type) {
return beanName + type.code();
StringBuilder sb = new StringBuilder(beanName.length() + 64);
sb.append(beanName);
if (tenantPartitionedCache) {
sb.append('.');
sb.append(tenantProvider.currentId());
}
sb.append(type.code());
return sb.toString();
}

private String key(String beanName, String collectionProperty, ServerCacheType type) {
StringBuilder sb = new StringBuilder(beanName.length() + 64);
sb.append(beanName);
if (tenantPartitionedCache) {
sb.append('.');
sb.append(tenantProvider.currentId());
}
if (collectionProperty != null) {
return beanName + "." + collectionProperty + type.code();
} else {
return beanName + type.code();
sb.append('.');
sb.append(collectionProperty);
}
sb.append(type.code());
return sb.toString();
}

/**
Expand All @@ -82,12 +110,17 @@ private ServerCache createCache(Class<?> beanType, ServerCacheType type, String
if (type == ServerCacheType.COLLECTION_IDS) {
lock.lock();
try {
collectIdCaches.computeIfAbsent(beanType.getName(), s -> new ConcurrentSkipListSet<>()).add(key);
collectIdCaches.computeIfAbsent(key(beanType.getName()), s -> new ConcurrentSkipListSet<>()).add(key);
} finally {
lock.unlock();
}
}
return cacheFactory.createCache(new ServerCacheConfig(type, key, shortName, options, tenantProvider, queryCacheEntryValidate));
if (tenantPartitionedCache) {
return cacheFactory.createCache(new ServerCacheConfig(type, key, shortName, options, null, queryCacheEntryValidate));
} else {
return cacheFactory.createCache(new ServerCacheConfig(type, key, shortName, options, tenantProvider, queryCacheEntryValidate));
}

}

void clearAll() {
Expand All @@ -103,7 +136,7 @@ public void clear(String name) {
clearIfExists(key(name, ServerCacheType.QUERY));
clearIfExists(key(name, ServerCacheType.BEAN));
clearIfExists(key(name, ServerCacheType.NATURAL_KEY));
Set<String> keys = collectIdCaches.get(name);
Set<String> keys = collectIdCaches.get(key(name));
if (keys != null) {
for (String collectionIdKey : keys) {
clearIfExists(collectionIdKey);
Expand Down Expand Up @@ -147,4 +180,7 @@ private ServerCacheOptions getBeanOptions(Class<?> cls) {
return beanDefault.copy(nearCache);
}

boolean isTenantPartitionedCache() {
return tenantPartitionedCache;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,8 @@ public ServerCache getBeanCache(Class<?> beanType) {
return cacheHolder.getCache(beanType, ServerCacheType.BEAN);
}

@Override
public boolean isTenantPartitionedCache() {
return cacheHolder.isTenantPartitionedCache();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,10 @@ public interface SpiCacheManager {
*/
void clearLocal(Class<?> beanType);

/**
* returns true, if this chacheManager runs in tenant partitioned mode
* @return
*/
boolean isTenantPartitionedCache();

}
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ public BeanDescriptor(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy) {
this.idOnlyReference = isIdOnlyReference(propertiesBaseScalar);
boolean noRelationships = propertiesOne.length + propertiesMany.length == 0;
this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly();
this.cacheHelp = new BeanDescriptorCacheHelp<>(this, owner.cacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
this.cacheHelp = BeanDescriptorCacheHelpPartitioned.create(this, owner.cacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
this.jsonHelp = initJsonHelp();
this.draftHelp = new BeanDescriptorDraftHelp<>(this);
this.docStoreAdapter = owner.createDocStoreBeanAdapter(this, deploy);
Expand Down
Loading

0 comments on commit a7db1f6

Please sign in to comment.