Skip to content

Commit

Permalink
feat: add high contention hint to cache spec (#186)
Browse files Browse the repository at this point in the history
* feat(api): add high contention hint to cache spec

* feat(cache2k): apply boostConcurrency according to contention flag

* feat(guava): configure concurrencyLevel given highContention

* feat(ehcache): use TickingTimeSource under highContention

* chore(core): add test case

* chore(ehcache): simplify unbounded cache creation

* chore(guava): use 64 concurrencyLevel under contention
  • Loading branch information
iProdigy committed Oct 9, 2023
1 parent bee091e commit dee4c05
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 12 deletions.
15 changes: 15 additions & 0 deletions api/src/main/java/io/github/xanthic/cache/api/ICacheSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,19 @@ public interface ICacheSpec<K, V> {
@Nullable
ScheduledExecutorService executor();

/**
* The forecasted contention of the cache.
* <p>
* If the cache will be mutated by many threads concurrently,
* this flag can be enabled to hint to certain providers to
* perform certain internal optimizations to boost performance,
* which could come at the cost of increased memory usage.
*
* @return whether providers should optimize for high contention
*/
@Nullable
default Boolean highContention() {
return null; // avoids breaking change
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import org.jetbrains.annotations.NotNull;

import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;

Expand Down Expand Up @@ -44,6 +43,8 @@ public final class CacheApiSpec<K, V> implements ICacheSpec<K, V> {

private ScheduledExecutorService executor;

private Boolean highContention;

@NotNull
public CacheProvider provider() {
// noinspection ConstantConditions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public void sizeEvictionTest() {
cache.put(String.valueOf(i), i);

// Hint to LRU/LFU impls like ehcache that 0 should be selected for eviction
for (int j = 0; j < i; j++) {
for (int j = 1; j < i; j++) {
cache.get(String.valueOf(i));
}
}
Expand Down Expand Up @@ -348,6 +348,17 @@ public void registeredAsDefaultTest() {
Assertions.assertEquals(provider.getClass(), CacheApiSettings.getInstance().getDefaultCacheProvider().getClass());
}

@Test
@DisplayName("Test whether cache can be built with contention flag and custom executor")
public void buildTest() {
Assertions.assertNotNull(
build(spec -> spec.highContention(true).maxSize(null))
);
Assertions.assertNotNull(
build(spec -> spec.highContention(true).executor(Executors.newSingleThreadScheduledExecutor()))
);
}

protected <K, V> Cache<K, V> build(Consumer<CacheApiSpec<K, V>> additionalSpec) {
Consumer<CacheApiSpec<K, V>> baseSpec = spec -> {
spec.provider(provider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public final class Cache2kProvider extends AbstractCacheProvider {
public <K, V> Cache<K, V> build(ICacheSpec<K, V> spec) {
//noinspection unchecked
Cache2kBuilder<K, V> builder = (Cache2kBuilder<K, V>) Cache2kBuilder.forUnknownTypes()
.disableStatistics(true); // avoid performance penalty since we don't offer an interface for these statistics
.disableStatistics(true) // avoid performance penalty since we don't offer an interface for these statistics
.boostConcurrency(Boolean.TRUE.equals(spec.highContention())); // utilize more memory to optimize for many threads performing mutations

if (spec.maxSize() != null) {
builder.entryCapacity(spec.maxSize());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.spi.time.TickingTimeSource;
import org.ehcache.event.EventType;
import org.ehcache.impl.internal.TimeSourceConfiguration;

import java.time.Duration;
import java.util.UUID;
Expand All @@ -29,11 +30,20 @@ public final class EhcacheProvider extends AbstractCacheProvider {

@Override
public <K, V> Cache<K, V> build(ICacheSpec<K, V> spec) {
CacheManager manager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
CacheManagerBuilder<CacheManager> managerBuilder = CacheManagerBuilder.newCacheManagerBuilder();
if (Boolean.TRUE.equals(spec.highContention()) && spec.expiryTime() != null) {
// https://www.ehcache.org/documentation/3.10/performance.html#time-source
managerBuilder = managerBuilder.using(
new TimeSourceConfiguration(new TickingTimeSource(1L, 1000L))
);
}
CacheManager manager = managerBuilder.build(true);

//noinspection unchecked
final CacheConfigurationBuilder<Object, Object>[] builder = new CacheConfigurationBuilder[] {
CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class, poolBuilder(spec.maxSize()))
CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
ResourcePoolsBuilder.heap(spec.maxSize() != null ? spec.maxSize() : Long.MAX_VALUE)
)
};

handleExpiration(spec.expiryTime(), spec.expiryType(), (time, type) -> {
Expand Down Expand Up @@ -71,12 +81,6 @@ public <K, V> Cache<K, V> build(ICacheSpec<K, V> spec) {
return delegate;
}

private static ResourcePoolsBuilder poolBuilder(Long maxSize) {
if (maxSize == null)
return ResourcePoolsBuilder.newResourcePoolsBuilder().heap(Runtime.getRuntime().maxMemory() / 2, MemoryUnit.B);
return ResourcePoolsBuilder.heap(maxSize);
}

private static RemovalCause getCause(EventType type) {
switch (type) {
case EVICTED:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public final class GuavaProvider extends AbstractCacheProvider {
public <K, V> Cache<K, V> build(ICacheSpec<K, V> spec) {
CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
if (spec.maxSize() != null) builder.maximumSize(spec.maxSize());
if (Boolean.TRUE.equals(spec.highContention())) {
// https://github.com/google/guava/issues/2063
builder.concurrencyLevel(64);
}
if (spec.removalListener() != null) {
//noinspection ConstantConditions
builder = builder.removalListener(e -> {
Expand Down

0 comments on commit dee4c05

Please sign in to comment.