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 L2 cache tenant awareness - add TenantAwareCache #2638

Merged
merged 1 commit into from
Apr 7, 2022
Merged
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
7 changes: 7 additions & 0 deletions ebean-api/src/main/java/io/ebean/cache/ServerCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,11 @@ default ServerCacheStatistics statistics(boolean reset) {
default void visit(MetricVisitor visitor) {
// do nothing by default
}

/**
* Unwrap the underlying ServerCache.
*/
default <T> T unwrap(Class<T> cls) {
return (T) this;
}
}
9 changes: 9 additions & 0 deletions ebean-api/src/main/java/io/ebean/cache/ServerCacheConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class ServerCacheConfig {
private final ServerCacheOptions cacheOptions;
private final CurrentTenantProvider tenantProvider;
private final QueryCacheEntryValidate queryCacheEntryValidate;
private final TenantAwareKey tenantAwareKey;

public ServerCacheConfig(ServerCacheType type, String cacheKey, String shortName, ServerCacheOptions cacheOptions, CurrentTenantProvider tenantProvider, QueryCacheEntryValidate queryCacheEntryValidate) {
this.type = type;
Expand All @@ -21,6 +22,14 @@ public ServerCacheConfig(ServerCacheType type, String cacheKey, String shortName
this.cacheOptions = cacheOptions;
this.tenantProvider = tenantProvider;
this.queryCacheEntryValidate = queryCacheEntryValidate;
this.tenantAwareKey = (tenantProvider == null) ? null : new TenantAwareKey(tenantProvider);
}

/**
* Return the ServerCache taking into account if multi-tenant is used.
*/
public ServerCache tenantAware(ServerCache cache) {
return tenantAwareKey == null ? cache : new TenantAwareCache(cache, tenantAwareKey);
}

/**
Expand Down
104 changes: 104 additions & 0 deletions ebean-api/src/main/java/io/ebean/cache/TenantAwareCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.ebean.cache;

import io.ebean.meta.MetricVisitor;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
* A ServerCache proxy that is tenant aware.
*/
public final class TenantAwareCache implements ServerCache {

private final ServerCache delegate;
private final TenantAwareKey tenantAwareKey;

/**
* Create given the TenantAwareKey and delegate cache to proxy to.
*
* @param delegate The cache to proxy to
* @param tenantAwareKey Provides tenant aware keys to use in the cache
*/
public TenantAwareCache(ServerCache delegate, TenantAwareKey tenantAwareKey) {
this.delegate = delegate;
this.tenantAwareKey = tenantAwareKey;
}

/**
* Return the underlying ServerCache that is being delegated to.
*/
@Override
public <T> T unwrap(Class<T> cls) {
return (T)delegate;
}

@Override
public void visit(MetricVisitor visitor) {
delegate.visit(visitor);
}

private Object key(Object key) {
return tenantAwareKey.key(key);
}

@Override
public Object get(Object id) {
return delegate.get(key(id));
}

@Override
public void put(Object id, Object value) {
delegate.put(key(id), value);
}

@Override
public void remove(Object id) {
delegate.remove(key(id));
}

@Override
public void clear() {
delegate.clear();
}

@Override
public int size() {
return delegate.size();
}

@Override
public int getHitRatio() {
return delegate.getHitRatio();
}

@Override
public ServerCacheStatistics getStatistics(boolean reset) {
return delegate.getStatistics(reset);
}

@Override
public Map<Object, Object> getAll(Set<Object> keys) {
Map<Object, Object> keyMapping = new HashMap<>(keys.size());
keys.forEach(k -> keyMapping.put(key(k), k));
Map<Object, Object> tmp = delegate.getAll(keyMapping.keySet());
Map<Object, Object> ret = new HashMap<>(keys.size());
// unwrap tenant info here
tmp.forEach((k,v)-> ret.put(((TenantAwareKey.CacheKey) k).key, v));
return ret;
}

@Override
public void putAll(Map<Object, Object> keyValues) {
Map<Object, Object> tmp = new HashMap<>();
keyValues.forEach((k, v) -> tmp.put(key(k), v));
delegate.putAll(tmp);
}

@Override
public void removeAll(Set<Object> keys) {
delegate.removeAll(keys.stream().map(this::key).collect(Collectors.toSet()));
}

}
122 changes: 122 additions & 0 deletions ebean-api/src/test/java/io/ebean/cache/TenantAwareCacheTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package io.ebean.cache;

import io.ebean.cache.TenantAwareKey.CacheKey;
import io.ebean.config.CurrentTenantProvider;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static org.assertj.core.api.Assertions.assertThat;

class TenantAwareCacheTest {

ServerCache serverCache;
TenantAwareCache cache;

TenantAwareCacheTest() {
TenantAwareKey key = new TenantAwareKey(new TenantProv());
this.serverCache = new Cache();
this.cache = new TenantAwareCache(serverCache, key);
}

@Test
void put_get_remove() {
cache.put("A", "a");
Object val = cache.get("A");
assertThat(val).isEqualTo("a");

CacheKey cacheKey = new CacheKey("A", 42);
Object val2 = serverCache.get(cacheKey);
assertThat(val2).isEqualTo("a");

cache.put("B", "bb");
assertThat(cache.size()).isEqualTo(2);
cache.remove("A");
assertThat(cache.get("A")).isNull();
assertThat(cache.size()).isEqualTo(1);

cache.clear();
assertThat(cache.size()).isEqualTo(0);
assertThat(cache.get("B")).isNull();
}

@Test
void putAll_getAll_removeAll() {
Map<Object,Object> map = new HashMap<>();
map.put("A", "a");
map.put("B", "b");
map.put("C", "c");

cache.putAll(map);
assertThat(cache.size()).isEqualTo(3);
assertThat(cache.get("A")).isEqualTo("a");
assertThat(serverCache.get(new CacheKey("A", 42))).isEqualTo("a");


Map<Object, Object> result = cache.getAll(Set.of("A", "B", "C", "D"));
assertThat(result).hasSize(3);
assertThat(result).containsOnlyKeys("A", "B", "C");
assertThat(result.values()).containsOnly("a", "b", "c");

cache.removeAll(Set.of("A", "C", "D"));
assertThat(cache.size()).isEqualTo(1);

assertThat(cache.get("B")).isEqualTo("b");
assertThat(serverCache.get(new CacheKey("B", 42))).isEqualTo("b");

cache.remove("B");
assertThat(cache.size()).isEqualTo(0);
}


static class TenantProv implements CurrentTenantProvider {

@Override
public Object currentId() {
return 42;
}
}

static class Cache implements ServerCache {

Map<Object, Object> map = new ConcurrentHashMap<>();

@Override
public Object get(Object id) {
return map.get(id);
}

@Override
public void put(Object id, Object value) {
map.put(id, value);
}

@Override
public void remove(Object id) {
map.remove(id);
}

@Override
public void clear() {
map.clear();
}

@Override
public int size() {
return map.size();
}

@Override
public int getHitRatio() {
return 0;
}

@Override
public ServerCacheStatistics getStatistics(boolean reset) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,12 @@ public class DefaultServerCache implements ServerCache {
private final int trimFrequency;
private final int maxIdleSecs;
private final int maxSecsToLive;
private final TenantAwareKey tenantAwareKey;

public DefaultServerCache(DefaultServerCacheConfig config) {
this.name = config.getName();
this.shortName = config.getShortName();
this.map = config.getMap();
this.maxSize = config.getMaxSize();
this.tenantAwareKey = new TenantAwareKey(config.getTenantProvider());
this.maxIdleSecs = config.getMaxIdleSecs();
this.maxSecsToLive = config.getMaxSecsToLive();
this.trimFrequency = config.determineTrimFrequency();
Expand Down Expand Up @@ -152,19 +150,12 @@ public void clear() {
map.clear();
}

/**
* Return the tenant aware key.
*/
protected Object key(Object id) {
return tenantAwareKey.key(id);
}

/**
* Return a value from the cache.
*/
@Override
public Object get(Object id) {
CacheEntry entry = getCacheEntry(id);
public Object get(Object key) {
CacheEntry entry = getCacheEntry(key);
if (entry == null) {
missCount.increment();
return null;
Expand All @@ -184,8 +175,8 @@ protected Object unwrapEntry(CacheEntry entry) {
/**
* Get the cache entry - override for query cache to validate dependent tables.
*/
protected CacheEntry getCacheEntry(Object id) {
final SoftReference<CacheEntry> ref = map.get(key(id));
protected CacheEntry getCacheEntry(Object key) {
final SoftReference<CacheEntry> ref = map.get(key);
return ref != null ? ref.get() : null;
}

Expand All @@ -198,8 +189,7 @@ public void putAll(Map<Object, Object> keyValues) {
* Put a value into the cache.
*/
@Override
public void put(Object id, Object value) {
Object key = key(id);
public void put(Object key, Object value) {
map.put(key, new SoftReference<>(new CacheEntry(key, value)));
putCount.increment();
}
Expand All @@ -208,8 +198,8 @@ public void put(Object id, Object value) {
* Remove an entry from the cache.
*/
@Override
public void remove(Object id) {
SoftReference<CacheEntry> entry = map.remove(key(id));
public void remove(Object key) {
SoftReference<CacheEntry> entry = map.remove(key);
if (entry != null && entry.get() != null) {
removeCount.increment();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import io.ebean.cache.QueryCacheEntryValidate;
import io.ebean.cache.ServerCacheConfig;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.config.CurrentTenantProvider;
import io.ebeaninternal.server.cache.DefaultServerCache.CacheEntry;

import java.lang.ref.SoftReference;
Expand Down Expand Up @@ -34,10 +33,6 @@ public DefaultServerCacheConfig(ServerCacheConfig config, Map<Object, SoftRefere
this.maxSize = options.getMaxSize();
}

public CurrentTenantProvider getTenantProvider() {
return config.getTenantProvider();
}

public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return config.getQueryCacheEntryValidate();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public ServerCache createCache(ServerCacheConfig config) {
if (executor != null) {
cache.periodicTrim(executor);
}
return cache;
return config.tenantAware(cache);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ protected Object unwrapEntry(CacheEntry entry) {
}

@Override
protected CacheEntry getCacheEntry(Object id) {
Object key = key(id);
protected CacheEntry getCacheEntry(Object key) {
final SoftReference<CacheEntry> ref = map.get(key);
CacheEntry entry = ref != null ? ref.get() : null;
if (entry == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public long get(boolean reset) {

@Override
public void visit(MetricVisitor visitor) {

long val = visitor.reset() ? count.sumThenReset() : count.sum();
if (val > 0) {
visitor.visitCount(new DCountMetricStats(name, val));
Expand Down
Loading