From b196615f2e26c51bc7d7694de6a34c2e1883b9c8 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 19 Oct 2023 10:20:13 -0700 Subject: [PATCH 01/19] [Tiered caching] Framework changes Signed-off-by: Sagar Upadhyaya --- .../org/opensearch/common/cache/Cache.java | 112 +++-- .../common/cache/RemovalNotification.java | 12 + .../cache/request/ShardRequestCache.java | 48 +- .../AbstractIndexShardCacheEntity.java | 19 +- .../org/opensearch/indices/CachingTier.java | 45 ++ .../opensearch/indices/DiskCachingTier.java | 18 + .../indices/DummyDiskCachingTier.java | 58 +++ .../indices/IndicesRequestCache.java | 73 +-- .../opensearch/indices/OnHeapCachingTier.java | 16 + .../indices/OpenSearchOnHeapCache.java | 124 +++++ .../java/org/opensearch/indices/TierType.java | 15 + .../indices/TieredCacheEventListener.java | 22 + .../opensearch/indices/TieredCacheLoader.java | 15 + .../indices/TieredCacheService.java | 34 ++ .../TieredCacheSpilloverStrategyService.java | 231 +++++++++ .../indices/IndicesServiceCloseTests.java | 6 +- ...redCacheSpilloverStrategyServiceTests.java | 458 ++++++++++++++++++ 17 files changed, 1198 insertions(+), 108 deletions(-) create mode 100644 server/src/main/java/org/opensearch/indices/CachingTier.java create mode 100644 server/src/main/java/org/opensearch/indices/DiskCachingTier.java create mode 100644 server/src/main/java/org/opensearch/indices/DummyDiskCachingTier.java create mode 100644 server/src/main/java/org/opensearch/indices/OnHeapCachingTier.java create mode 100644 server/src/main/java/org/opensearch/indices/OpenSearchOnHeapCache.java create mode 100644 server/src/main/java/org/opensearch/indices/TierType.java create mode 100644 server/src/main/java/org/opensearch/indices/TieredCacheEventListener.java create mode 100644 server/src/main/java/org/opensearch/indices/TieredCacheLoader.java create mode 100644 server/src/main/java/org/opensearch/indices/TieredCacheService.java create mode 100644 server/src/main/java/org/opensearch/indices/TieredCacheSpilloverStrategyService.java create mode 100644 server/src/test/java/org/opensearch/indices/TieredCacheSpilloverStrategyServiceTests.java diff --git a/server/src/main/java/org/opensearch/common/cache/Cache.java b/server/src/main/java/org/opensearch/common/cache/Cache.java index 0b2b608b55df0..9e91866cde2df 100644 --- a/server/src/main/java/org/opensearch/common/cache/Cache.java +++ b/server/src/main/java/org/opensearch/common/cache/Cache.java @@ -422,68 +422,74 @@ public V computeIfAbsent(K key, CacheLoader loader) throws ExecutionExcept } }); if (value == null) { - // we need to synchronize loading of a value for a given key; however, holding the segment lock while - // invoking load can lead to deadlock against another thread due to dependent key loading; therefore, we - // need a mechanism to ensure that load is invoked at most once, but we are not invoking load while holding - // the segment lock; to do this, we atomically put a future in the map that can load the value, and then - // get the value from this future on the thread that won the race to place the future into the segment map - CacheSegment segment = getCacheSegment(key); - CompletableFuture> future; - CompletableFuture> completableFuture = new CompletableFuture<>(); + value = compute(key, loader); + } + return value; + } - try (ReleasableLock ignored = segment.writeLock.acquire()) { - future = segment.map.putIfAbsent(key, completableFuture); - } + public V compute(K key, CacheLoader loader) throws ExecutionException { + long now = now(); + // we need to synchronize loading of a value for a given key; however, holding the segment lock while + // invoking load can lead to deadlock against another thread due to dependent key loading; therefore, we + // need a mechanism to ensure that load is invoked at most once, but we are not invoking load while holding + // the segment lock; to do this, we atomically put a future in the map that can load the value, and then + // get the value from this future on the thread that won the race to place the future into the segment map + CacheSegment segment = getCacheSegment(key); + CompletableFuture> future; + CompletableFuture> completableFuture = new CompletableFuture<>(); - BiFunction, Throwable, ? extends V> handler = (ok, ex) -> { - if (ok != null) { - try (ReleasableLock ignored = lruLock.acquire()) { - promote(ok, now); - } - return ok.value; - } else { - try (ReleasableLock ignored = segment.writeLock.acquire()) { - CompletableFuture> sanity = segment.map.get(key); - if (sanity != null && sanity.isCompletedExceptionally()) { - segment.map.remove(key); - } - } - return null; - } - }; + try (ReleasableLock ignored = segment.writeLock.acquire()) { + future = segment.map.putIfAbsent(key, completableFuture); + } - CompletableFuture completableValue; - if (future == null) { - future = completableFuture; - completableValue = future.handle(handler); - V loaded; - try { - loaded = loader.load(key); - } catch (Exception e) { - future.completeExceptionally(e); - throw new ExecutionException(e); - } - if (loaded == null) { - NullPointerException npe = new NullPointerException("loader returned a null value"); - future.completeExceptionally(npe); - throw new ExecutionException(npe); - } else { - future.complete(new Entry<>(key, loaded, now)); + BiFunction, Throwable, ? extends V> handler = (ok, ex) -> { + if (ok != null) { + try (ReleasableLock ignored = lruLock.acquire()) { + promote(ok, now); } + return ok.value; } else { - completableValue = future.handle(handler); + try (ReleasableLock ignored = segment.writeLock.acquire()) { + CompletableFuture> sanity = segment.map.get(key); + if (sanity != null && sanity.isCompletedExceptionally()) { + segment.map.remove(key); + } + } + return null; } + }; + CompletableFuture completableValue; + if (future == null) { + future = completableFuture; + completableValue = future.handle(handler); + V loaded; try { - value = completableValue.get(); - // check to ensure the future hasn't been completed with an exception - if (future.isCompletedExceptionally()) { - future.get(); // call get to force the exception to be thrown for other concurrent callers - throw new IllegalStateException("the future was completed exceptionally but no exception was thrown"); - } - } catch (InterruptedException e) { - throw new IllegalStateException(e); + loaded = loader.load(key); + } catch (Exception e) { + future.completeExceptionally(e); + throw new ExecutionException(e); } + if (loaded == null) { + NullPointerException npe = new NullPointerException("loader returned a null value"); + future.completeExceptionally(npe); + throw new ExecutionException(npe); + } else { + future.complete(new Entry<>(key, loaded, now)); + } + } else { + completableValue = future.handle(handler); + } + V value; + try { + value = completableValue.get(); + // check to ensure the future hasn't been completed with an exception + if (future.isCompletedExceptionally()) { + future.get(); // call get to force the exception to be thrown for other concurrent callers + throw new IllegalStateException("the future was completed exceptionally but no exception was thrown"); + } + } catch (InterruptedException e) { + throw new IllegalStateException(e); } return value; } diff --git a/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java b/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java index 6d355b2122460..71e240064c6ae 100644 --- a/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java +++ b/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java @@ -32,6 +32,8 @@ package org.opensearch.common.cache; +import org.opensearch.indices.TierType; + /** * Notification when an element is removed from the cache * @@ -42,11 +44,17 @@ public class RemovalNotification { private final K key; private final V value; private final RemovalReason removalReason; + private final TierType tierType; public RemovalNotification(K key, V value, RemovalReason removalReason) { + this(key, value, removalReason, TierType.ON_HEAP); + } + + public RemovalNotification(K key, V value, RemovalReason removalReason, TierType tierType) { this.key = key; this.value = value; this.removalReason = removalReason; + this.tierType = tierType; } public K getKey() { @@ -60,4 +68,8 @@ public V getValue() { public RemovalReason getRemovalReason() { return removalReason; } + + public TierType getTierType() { + return tierType; + } } diff --git a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java index b13eec79c2be8..1beef5217355f 100644 --- a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java +++ b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java @@ -35,6 +35,9 @@ import org.apache.lucene.util.Accountable; import org.opensearch.common.metrics.CounterMetric; import org.opensearch.core.common.bytes.BytesReference; +import org.opensearch.indices.TierType; + +import java.util.EnumMap; /** * Tracks the portion of the request cache in use for a particular shard. @@ -43,30 +46,39 @@ */ public final class ShardRequestCache { - final CounterMetric evictionsMetric = new CounterMetric(); - final CounterMetric totalMetric = new CounterMetric(); - final CounterMetric hitCount = new CounterMetric(); - final CounterMetric missCount = new CounterMetric(); + private EnumMap statsHolder = new EnumMap<>(TierType.class); + + public ShardRequestCache() { + for (TierType tierType : TierType.values()) { + statsHolder.put(tierType, new StatsHolder()); + } + } public RequestCacheStats stats() { - return new RequestCacheStats(totalMetric.count(), evictionsMetric.count(), hitCount.count(), missCount.count()); + // TODO: Change RequestCacheStats to support disk tier stats. + return new RequestCacheStats( + statsHolder.get(TierType.ON_HEAP).totalMetric.count(), + statsHolder.get(TierType.ON_HEAP).evictionsMetric.count(), + statsHolder.get(TierType.ON_HEAP).hitCount.count(), + statsHolder.get(TierType.ON_HEAP).missCount.count() + ); } - public void onHit() { - hitCount.inc(); + public void onHit(TierType tierType) { + statsHolder.get(tierType).hitCount.inc(); } - public void onMiss() { - missCount.inc(); + public void onMiss(TierType tierType) { + statsHolder.get(tierType).missCount.inc(); } - public void onCached(Accountable key, BytesReference value) { - totalMetric.inc(key.ramBytesUsed() + value.ramBytesUsed()); + public void onCached(Accountable key, BytesReference value, TierType tierType) { + statsHolder.get(tierType).totalMetric.inc(key.ramBytesUsed() + value.ramBytesUsed()); } - public void onRemoval(Accountable key, BytesReference value, boolean evicted) { + public void onRemoval(Accountable key, BytesReference value, boolean evicted, TierType tierType) { if (evicted) { - evictionsMetric.inc(); + statsHolder.get(tierType).evictionsMetric.inc(); } long dec = 0; if (key != null) { @@ -75,6 +87,14 @@ public void onRemoval(Accountable key, BytesReference value, boolean evicted) { if (value != null) { dec += value.ramBytesUsed(); } - totalMetric.dec(dec); + statsHolder.get(tierType).totalMetric.dec(dec); + } + + static class StatsHolder { + + final CounterMetric evictionsMetric = new CounterMetric(); + final CounterMetric totalMetric = new CounterMetric(); + final CounterMetric hitCount = new CounterMetric(); + final CounterMetric missCount = new CounterMetric(); } } diff --git a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java index bb1201cb910a9..2eef16df2bb9a 100644 --- a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java +++ b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java @@ -51,22 +51,27 @@ abstract class AbstractIndexShardCacheEntity implements IndicesRequestCache.Cach protected abstract ShardRequestCache stats(); @Override - public final void onCached(IndicesRequestCache.Key key, BytesReference value) { - stats().onCached(key, value); + public final void onCached(IndicesRequestCache.Key key, BytesReference value, TierType tierType) { + stats().onCached(key, value, tierType); } @Override - public final void onHit() { - stats().onHit(); + public final void onHit(TierType tierType) { + stats().onHit(tierType); } @Override - public final void onMiss() { - stats().onMiss(); + public final void onMiss(TierType tierType) { + stats().onMiss(tierType); } @Override public final void onRemoval(RemovalNotification notification) { - stats().onRemoval(notification.getKey(), notification.getValue(), notification.getRemovalReason() == RemovalReason.EVICTED); + stats().onRemoval( + notification.getKey(), + notification.getValue(), + notification.getRemovalReason() == RemovalReason.EVICTED, + notification.getTierType() + ); } } diff --git a/server/src/main/java/org/opensearch/indices/CachingTier.java b/server/src/main/java/org/opensearch/indices/CachingTier.java new file mode 100644 index 0000000000000..7bf7294467114 --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/CachingTier.java @@ -0,0 +1,45 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +import org.opensearch.common.cache.RemovalListener; + +/** + * Caching tier interface. Can be implemented/extended by concrete classes to provide different flavors of cache like + * onHeap, disk etc. + * @param Type of key + * @param Type of value + */ +public interface CachingTier { + + V get(K key); + + void put(K key, V value); + + V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception; + + void invalidate(K key); + + V compute(K key, TieredCacheLoader loader) throws Exception; + + void setRemovalListener(RemovalListener removalListener); + + void invalidateAll(); + + Iterable keys(); + + int count(); + + TierType getTierType(); + + /** + * Force any outstanding size-based and time-based evictions to occur + */ + default void refresh() {} +} diff --git a/server/src/main/java/org/opensearch/indices/DiskCachingTier.java b/server/src/main/java/org/opensearch/indices/DiskCachingTier.java new file mode 100644 index 0000000000000..abc8952c0b8e2 --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/DiskCachingTier.java @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +/** + * This is specific to disk caching tier and can be used to add methods which are specific to disk tier. + * @param Type of key + * @param Type of value + */ +public interface DiskCachingTier extends CachingTier { + +} diff --git a/server/src/main/java/org/opensearch/indices/DummyDiskCachingTier.java b/server/src/main/java/org/opensearch/indices/DummyDiskCachingTier.java new file mode 100644 index 0000000000000..26a78b6c61920 --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/DummyDiskCachingTier.java @@ -0,0 +1,58 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +import org.opensearch.common.cache.RemovalListener; + +import java.util.Collections; + +public class DummyDiskCachingTier implements CachingTier { + + @Override + public V get(K key) { + return null; + } + + @Override + public void put(K key, V value) {} + + @Override + public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { + return null; + } + + @Override + public void invalidate(K key) {} + + @Override + public V compute(K key, TieredCacheLoader loader) throws Exception { + return null; + } + + @Override + public void setRemovalListener(RemovalListener removalListener) {} + + @Override + public void invalidateAll() {} + + @Override + public Iterable keys() { + return Collections::emptyIterator; + } + + @Override + public int count() { + return 0; + } + + @Override + public TierType getTierType() { + return TierType.DISK; + } +} diff --git a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java index 629cea102a8b2..6781d3fa0eb2a 100644 --- a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java +++ b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java @@ -39,10 +39,6 @@ import org.apache.lucene.util.Accountable; import org.apache.lucene.util.RamUsageEstimator; import org.opensearch.common.CheckedSupplier; -import org.opensearch.common.cache.Cache; -import org.opensearch.common.cache.CacheBuilder; -import org.opensearch.common.cache.CacheLoader; -import org.opensearch.common.cache.RemovalListener; import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.lucene.index.OpenSearchDirectoryReader; import org.opensearch.common.settings.Setting; @@ -78,7 +74,7 @@ * * @opensearch.internal */ -public final class IndicesRequestCache implements RemovalListener, Closeable { +public final class IndicesRequestCache implements TieredCacheEventListener, Closeable { private static final Logger logger = LogManager.getLogger(IndicesRequestCache.class); @@ -107,25 +103,27 @@ public final class IndicesRequestCache implements RemovalListener keysToClean = ConcurrentCollections.newConcurrentSet(); private final ByteSizeValue size; private final TimeValue expire; - private final Cache cache; + private final TieredCacheService tieredCacheService; IndicesRequestCache(Settings settings) { this.size = INDICES_CACHE_QUERY_SIZE.get(settings); this.expire = INDICES_CACHE_QUERY_EXPIRE.exists(settings) ? INDICES_CACHE_QUERY_EXPIRE.get(settings) : null; long sizeInBytes = size.getBytes(); - CacheBuilder cacheBuilder = CacheBuilder.builder() - .setMaximumWeight(sizeInBytes) - .weigher((k, v) -> k.ramBytesUsed() + v.ramBytesUsed()) - .removalListener(this); - if (expire != null) { - cacheBuilder.setExpireAfterAccess(expire); - } - cache = cacheBuilder.build(); + + // Initialize onHeap cache tier first. + OnHeapCachingTier openSearchOnHeapCache = new OpenSearchOnHeapCache.Builder().setWeigher( + (k, v) -> k.ramBytesUsed() + v.ramBytesUsed() + ).setMaximumWeight(sizeInBytes).setExpireAfterAccess(expire).build(); + + // Initialize tiered cache service. TODO: Enable Disk tier when tiered support is turned on. + tieredCacheService = new TieredCacheSpilloverStrategyService.Builder().setOnHeapCachingTier( + openSearchOnHeapCache + ).setTieredCacheEventListener(this).build(); } @Override public void close() { - cache.invalidateAll(); + tieredCacheService.invalidateAll(); } void clear(CacheEntity entity) { @@ -133,11 +131,26 @@ void clear(CacheEntity entity) { cleanCache(); } + @Override + public void onMiss(Key key, TierType tierType) { + key.entity.onMiss(tierType); + } + @Override public void onRemoval(RemovalNotification notification) { notification.getKey().entity.onRemoval(notification); } + @Override + public void onHit(Key key, BytesReference value, TierType tierType) { + key.entity.onHit(tierType); + } + + @Override + public void onCached(Key key, BytesReference value, TierType tierType) { + key.entity.onCached(key, value, tierType); + } + BytesReference getOrCompute( CacheEntity cacheEntity, CheckedSupplier loader, @@ -147,9 +160,8 @@ BytesReference getOrCompute( assert reader.getReaderCacheHelper() != null; final Key key = new Key(cacheEntity, reader.getReaderCacheHelper().getKey(), cacheKey); Loader cacheLoader = new Loader(cacheEntity, loader); - BytesReference value = cache.computeIfAbsent(key, cacheLoader); + BytesReference value = tieredCacheService.computeIfAbsent(key, cacheLoader); if (cacheLoader.isLoaded()) { - key.entity.onMiss(); // see if its the first time we see this reader, and make sure to register a cleanup key CleanupKey cleanupKey = new CleanupKey(cacheEntity, reader.getReaderCacheHelper().getKey()); if (!registeredClosedListeners.containsKey(cleanupKey)) { @@ -158,8 +170,6 @@ BytesReference getOrCompute( OpenSearchDirectoryReader.addReaderCloseListener(reader, cleanupKey); } } - } else { - key.entity.onHit(); } return value; } @@ -172,7 +182,7 @@ BytesReference getOrCompute( */ void invalidate(CacheEntity cacheEntity, DirectoryReader reader, BytesReference cacheKey) { assert reader.getReaderCacheHelper() != null; - cache.invalidate(new Key(cacheEntity, reader.getReaderCacheHelper().getKey(), cacheKey)); + tieredCacheService.invalidate(new Key(cacheEntity, reader.getReaderCacheHelper().getKey(), cacheKey)); } /** @@ -180,7 +190,7 @@ void invalidate(CacheEntity cacheEntity, DirectoryReader reader, BytesReference * * @opensearch.internal */ - private static class Loader implements CacheLoader { + private static class Loader implements org.opensearch.indices.TieredCacheLoader { private final CacheEntity entity; private final CheckedSupplier loader; @@ -198,7 +208,6 @@ public boolean isLoaded() { @Override public BytesReference load(Key key) throws Exception { BytesReference value = loader.get(); - entity.onCached(key, value); loaded = true; return value; } @@ -212,7 +221,7 @@ interface CacheEntity extends Accountable { /** * Called after the value was loaded. */ - void onCached(Key key, BytesReference value); + void onCached(Key key, BytesReference value, TierType tierType); /** * Returns true iff the resource behind this entity is still open ie. @@ -229,12 +238,12 @@ interface CacheEntity extends Accountable { /** * Called each time this entity has a cache hit. */ - void onHit(); + void onHit(TierType tierType); /** * Called each time this entity has a cache miss. */ - void onMiss(); + void onMiss(TierType tierType); /** * Called when this entity instance is removed @@ -247,7 +256,7 @@ interface CacheEntity extends Accountable { * * @opensearch.internal */ - static class Key implements Accountable { + public static class Key implements Accountable { private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(Key.class); public final CacheEntity entity; // use as identity equality @@ -328,6 +337,9 @@ public int hashCode() { } } + /** + * Logic to clean up in-memory cache. + */ synchronized void cleanCache() { final Set currentKeysToClean = new HashSet<>(); final Set currentFullClean = new HashSet<>(); @@ -344,7 +356,7 @@ synchronized void cleanCache() { } } if (!currentKeysToClean.isEmpty() || !currentFullClean.isEmpty()) { - for (Iterator iterator = cache.keys().iterator(); iterator.hasNext();) { + for (Iterator iterator = tieredCacheService.getOnHeapCachingTier().keys().iterator(); iterator.hasNext();) { Key key = iterator.next(); if (currentFullClean.contains(key.entity.getCacheIdentity())) { iterator.remove(); @@ -355,15 +367,14 @@ synchronized void cleanCache() { } } } - - cache.refresh(); + tieredCacheService.getOnHeapCachingTier().refresh(); } /** * Returns the current size of the cache */ - int count() { - return cache.count(); + long count() { + return tieredCacheService.count(); } int numRegisteredCloseListeners() { // for testing diff --git a/server/src/main/java/org/opensearch/indices/OnHeapCachingTier.java b/server/src/main/java/org/opensearch/indices/OnHeapCachingTier.java new file mode 100644 index 0000000000000..f7d68c27c1904 --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/OnHeapCachingTier.java @@ -0,0 +1,16 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +/** + * This is specific to onHeap caching tier and can be used to add methods which are specific to this tier. + * @param Type of key + * @param Type of value + */ +public interface OnHeapCachingTier extends CachingTier {} diff --git a/server/src/main/java/org/opensearch/indices/OpenSearchOnHeapCache.java b/server/src/main/java/org/opensearch/indices/OpenSearchOnHeapCache.java new file mode 100644 index 0000000000000..189b741ed1696 --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/OpenSearchOnHeapCache.java @@ -0,0 +1,124 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +import org.opensearch.common.cache.Cache; +import org.opensearch.common.cache.CacheBuilder; +import org.opensearch.common.cache.RemovalListener; +import org.opensearch.common.cache.RemovalNotification; +import org.opensearch.common.unit.TimeValue; + +import java.util.concurrent.ExecutionException; +import java.util.function.ToLongBiFunction; + +public class OpenSearchOnHeapCache implements OnHeapCachingTier, RemovalListener { + + private final Cache cache; + private RemovalListener removalListener; + + private OpenSearchOnHeapCache(long maxWeightInBytes, ToLongBiFunction weigher, TimeValue expireAfterAcess) { + CacheBuilder cacheBuilder = CacheBuilder.builder() + .setMaximumWeight(maxWeightInBytes) + .weigher(weigher) + .removalListener(this); + if (expireAfterAcess != null) { + cacheBuilder.setExpireAfterAccess(expireAfterAcess); + } + cache = cacheBuilder.build(); + } + + @Override + public void setRemovalListener(RemovalListener removalListener) { + this.removalListener = removalListener; + } + + @Override + public void invalidateAll() { + cache.invalidateAll(); + } + + @Override + public Iterable keys() { + return this.cache.keys(); + } + + @Override + public int count() { + return cache.count(); + } + + @Override + public TierType getTierType() { + return TierType.ON_HEAP; + } + + @Override + public V get(K key) { + return cache.get(key); + } + + @Override + public void put(K key, V value) { + cache.put(key, value); + } + + @Override + public V computeIfAbsent(K key, TieredCacheLoader loader) throws ExecutionException { + return cache.computeIfAbsent(key, key1 -> loader.load(key)); + } + + @Override + public void invalidate(K key) { + cache.invalidate(key); + } + + @Override + public V compute(K key, TieredCacheLoader loader) throws Exception { + return cache.compute(key, key1 -> loader.load(key)); + } + + @Override + public void refresh() { + cache.refresh(); + } + + @Override + public void onRemoval(RemovalNotification notification) { + removalListener.onRemoval(notification); + } + + public static class Builder { + private long maxWeightInBytes; + + private ToLongBiFunction weigher; + + private TimeValue expireAfterAcess; + + public Builder() {} + + public Builder setMaximumWeight(long sizeInBytes) { + this.maxWeightInBytes = sizeInBytes; + return this; + } + + public Builder setWeigher(ToLongBiFunction weigher) { + this.weigher = weigher; + return this; + } + + public Builder setExpireAfterAccess(TimeValue expireAfterAcess) { + this.expireAfterAcess = expireAfterAcess; + return this; + } + + public OpenSearchOnHeapCache build() { + return new OpenSearchOnHeapCache(maxWeightInBytes, weigher, expireAfterAcess); + } + } +} diff --git a/server/src/main/java/org/opensearch/indices/TierType.java b/server/src/main/java/org/opensearch/indices/TierType.java new file mode 100644 index 0000000000000..9a286fd26151b --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/TierType.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +public enum TierType { + + ON_HEAP, + DISK; +} diff --git a/server/src/main/java/org/opensearch/indices/TieredCacheEventListener.java b/server/src/main/java/org/opensearch/indices/TieredCacheEventListener.java new file mode 100644 index 0000000000000..084ac5a57e0d3 --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/TieredCacheEventListener.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +import org.opensearch.common.cache.RemovalNotification; + +public interface TieredCacheEventListener { + + void onMiss(K key, TierType tierType); + + void onRemoval(RemovalNotification notification); + + void onHit(K key, V value, TierType tierType); + + void onCached(K key, V value, TierType tierType); +} diff --git a/server/src/main/java/org/opensearch/indices/TieredCacheLoader.java b/server/src/main/java/org/opensearch/indices/TieredCacheLoader.java new file mode 100644 index 0000000000000..f6bb1a74e973e --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/TieredCacheLoader.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +public interface TieredCacheLoader { + V load(K key) throws Exception; + + boolean isLoaded(); +} diff --git a/server/src/main/java/org/opensearch/indices/TieredCacheService.java b/server/src/main/java/org/opensearch/indices/TieredCacheService.java new file mode 100644 index 0000000000000..59e5e0e00b6c1 --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/TieredCacheService.java @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +import java.util.Optional; + +/** + * This service encapsulates all logic to write/fetch to/from appropriate tiers. Can be implemented with different + * flavors like spillover etc. + * @param Type of key + * @param Type of value + */ +public interface TieredCacheService { + + V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception; + + V get(K key); + + void invalidate(K key); + + void invalidateAll(); + + long count(); + + OnHeapCachingTier getOnHeapCachingTier(); + + Optional> getDiskCachingTier(); +} diff --git a/server/src/main/java/org/opensearch/indices/TieredCacheSpilloverStrategyService.java b/server/src/main/java/org/opensearch/indices/TieredCacheSpilloverStrategyService.java new file mode 100644 index 0000000000000..7799170a1ede9 --- /dev/null +++ b/server/src/main/java/org/opensearch/indices/TieredCacheSpilloverStrategyService.java @@ -0,0 +1,231 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +import org.opensearch.common.cache.RemovalListener; +import org.opensearch.common.cache.RemovalNotification; +import org.opensearch.common.cache.RemovalReason; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +/** + * This service spillover the evicted items from upper tier to lower tier. For now, we are spilling the in-memory + * cache items to disk tier cache. + * @param Type of key + * @param Type of value + */ +public class TieredCacheSpilloverStrategyService implements TieredCacheService, RemovalListener { + + private final OnHeapCachingTier onHeapCachingTier; + + /** + * Optional in case tiered caching is turned off. + */ + private final Optional> diskCachingTier; + private final TieredCacheEventListener tieredCacheEventListener; + + /** + * Maintains caching tiers in order of get calls. + */ + private final List> cachingTierList; + + private TieredCacheSpilloverStrategyService( + OnHeapCachingTier onHeapCachingTier, + DiskCachingTier diskCachingTier, + TieredCacheEventListener tieredCacheEventListener + ) { + this.onHeapCachingTier = Objects.requireNonNull(onHeapCachingTier); + this.diskCachingTier = Optional.ofNullable(diskCachingTier); + this.tieredCacheEventListener = Objects.requireNonNull(tieredCacheEventListener); + this.cachingTierList = this.diskCachingTier.map(diskTier -> Arrays.asList(onHeapCachingTier, diskTier)) + .orElse(List.of(onHeapCachingTier)); + setRemovalListeners(); + } + + /** + * This method logic is divided into 2 parts: + * 1. First check whether key is present or not in desired tier. If yes, return the value. + * 2. If the key is not present, then add the key/value pair to onHeap cache. + * @param key Key for lookup. + * @param loader Used to load value in case it is not present in any tier. + * @return value + * @throws Exception + */ + @Override + public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { + CacheValue cacheValue = getValueFromTierCache(true).apply(key); + if (cacheValue == null) { + // Add the value to the onHeap cache. Any items if evicted will be moved to lower tier. + V value = onHeapCachingTier.compute(key, loader); + tieredCacheEventListener.onCached(key, value, TierType.ON_HEAP); + return value; + } + return cacheValue.value; + } + + @Override + public V get(K key) { + CacheValue cacheValue = getValueFromTierCache(true).apply(key); + if (cacheValue == null) { + return null; + } + return cacheValue.value; + } + + /** + * First fetches the tier type which has this key. And then invalidate accordingly. + * @param key + */ + @Override + public void invalidate(K key) { + // We don't need to track hits/misses in this case. + CacheValue cacheValue = getValueFromTierCache(false).apply(key); + if (cacheValue != null) { + switch (cacheValue.source) { + case ON_HEAP: + onHeapCachingTier.invalidate(key); + break; + case DISK: + diskCachingTier.ifPresent(diskTier -> diskTier.invalidate(key)); + break; + default: + break; + } + } + } + + @Override + public void invalidateAll() { + for (CachingTier cachingTier : cachingTierList) { + cachingTier.invalidateAll(); + } + } + + /** + * Returns the total count of items present in all cache tiers. + * @return total count of items in cache + */ + @Override + public long count() { + long totalCount = 0; + for (CachingTier cachingTier : cachingTierList) { + totalCount += cachingTier.count(); + } + return totalCount; + } + + /** + * Called whenever an item is evicted from any cache tier. If the item was evicted from onHeap cache, it is moved + * to disk tier cache. In case it was evicted from disk tier cache, it will discarded. + * @param notification Contains info about the removal like reason, key/value etc. + */ + @Override + public void onRemoval(RemovalNotification notification) { + if (RemovalReason.EVICTED.equals(notification.getRemovalReason())) { + switch (notification.getTierType()) { + case ON_HEAP: + diskCachingTier.ifPresent(diskTier -> { + diskTier.put(notification.getKey(), notification.getValue()); + tieredCacheEventListener.onCached(notification.getKey(), notification.getValue(), TierType.DISK); + }); + break; + default: + break; + } + } + tieredCacheEventListener.onRemoval(notification); + } + + @Override + public OnHeapCachingTier getOnHeapCachingTier() { + return this.onHeapCachingTier; + } + + @Override + public Optional> getDiskCachingTier() { + return this.diskCachingTier; + } + + /** + * Register this service as a listener to removal events from different caching tiers. + */ + private void setRemovalListeners() { + for (CachingTier cachingTier : cachingTierList) { + cachingTier.setRemovalListener(this); + } + } + + private Function> getValueFromTierCache(boolean trackStats) { + return key -> { + for (CachingTier cachingTier : cachingTierList) { + V value = cachingTier.get(key); + if (value != null) { + if (trackStats) { + tieredCacheEventListener.onHit(key, value, cachingTier.getTierType()); + } + return new CacheValue<>(value, cachingTier.getTierType()); + } + if (trackStats) { + tieredCacheEventListener.onMiss(key, cachingTier.getTierType()); + } + } + return null; + }; + } + + /** + * Represents a cache value along with its associated tier type where it is stored. + * @param Type of value. + */ + public static class CacheValue { + V value; + TierType source; + + CacheValue(V value, TierType source) { + this.value = value; + this.source = source; + } + } + + public static class Builder { + private OnHeapCachingTier onHeapCachingTier; + private DiskCachingTier diskCachingTier; + private TieredCacheEventListener tieredCacheEventListener; + + public Builder() {} + + public Builder setOnHeapCachingTier(OnHeapCachingTier onHeapCachingTier) { + this.onHeapCachingTier = onHeapCachingTier; + return this; + } + + public Builder setOnDiskCachingTier(DiskCachingTier diskCachingTier) { + this.diskCachingTier = diskCachingTier; + return this; + } + + public Builder setTieredCacheEventListener(TieredCacheEventListener tieredCacheEventListener) { + this.tieredCacheEventListener = tieredCacheEventListener; + return this; + } + + public TieredCacheSpilloverStrategyService build() { + return new TieredCacheSpilloverStrategyService( + this.onHeapCachingTier, + this.diskCachingTier, + this.tieredCacheEventListener + ); + } + } + +} diff --git a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java index 415844dccb611..2fe1f87f5d828 100644 --- a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java +++ b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java @@ -321,7 +321,7 @@ public long ramBytesUsed() { } @Override - public void onCached(Key key, BytesReference value) {} + public void onCached(Key key, BytesReference value, TierType tierType) {} @Override public boolean isOpen() { @@ -334,10 +334,10 @@ public Object getCacheIdentity() { } @Override - public void onHit() {} + public void onHit(TierType tierType) {} @Override - public void onMiss() {} + public void onMiss(TierType tierType) {} @Override public void onRemoval(RemovalNotification notification) {} diff --git a/server/src/test/java/org/opensearch/indices/TieredCacheSpilloverStrategyServiceTests.java b/server/src/test/java/org/opensearch/indices/TieredCacheSpilloverStrategyServiceTests.java new file mode 100644 index 0000000000000..4c4c7f195ba31 --- /dev/null +++ b/server/src/test/java/org/opensearch/indices/TieredCacheSpilloverStrategyServiceTests.java @@ -0,0 +1,458 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.indices; + +import org.opensearch.common.cache.RemovalListener; +import org.opensearch.common.cache.RemovalNotification; +import org.opensearch.common.cache.RemovalReason; +import org.opensearch.common.metrics.CounterMetric; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class TieredCacheSpilloverStrategyServiceTests extends OpenSearchTestCase { + + public void testComputeAndAbsentWithoutAnyOnHeapCacheEviction() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); + TieredCacheSpilloverStrategyService spilloverStrategyService = intializeTieredCacheService( + onHeapCacheSize, + randomIntBetween(1, 4), + eventListener + ); + int numOfItems1 = randomIntBetween(1, onHeapCacheSize / 2 - 1); + List keys = new ArrayList<>(); + // Put values in cache. + for (int iter = 0; iter < numOfItems1; iter++) { + String key = UUID.randomUUID().toString(); + keys.add(key); + TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); + spilloverStrategyService.computeIfAbsent(key, tieredCacheLoader); + } + assertEquals(numOfItems1, eventListener.enumMap.get(TierType.ON_HEAP).missCount.count()); + assertEquals(0, eventListener.enumMap.get(TierType.ON_HEAP).hitCount.count()); + assertEquals(0, eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count()); + + // Try to hit cache again with some randomization. + int numOfItems2 = randomIntBetween(1, onHeapCacheSize / 2 - 1); + int cacheHit = 0; + int cacheMiss = 0; + for (int iter = 0; iter < numOfItems2; iter++) { + if (randomBoolean()) { + // Hit cache with stored key + cacheHit++; + int index = randomIntBetween(0, keys.size() - 1); + spilloverStrategyService.computeIfAbsent(keys.get(index), getTieredCacheLoader()); + } else { + // Hit cache with randomized key which is expected to miss cache always. + spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), getTieredCacheLoader()); + cacheMiss++; + } + } + assertEquals(cacheHit, eventListener.enumMap.get(TierType.ON_HEAP).hitCount.count()); + assertEquals(numOfItems1 + cacheMiss, eventListener.enumMap.get(TierType.ON_HEAP).missCount.count()); + assertEquals(0, eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count()); + } + + public void testComputeAndAbsentWithEvictionsFromOnHeapCache() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(60, 100); + int totalSize = onHeapCacheSize + diskCacheSize; + MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); + TieredCacheSpilloverStrategyService spilloverStrategyService = intializeTieredCacheService( + onHeapCacheSize, + diskCacheSize, + eventListener + ); + + // Put values in cache more than it's size and cause evictions from onHeap. + int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); + List onHeapKeys = new ArrayList<>(); + List diskTierKeys = new ArrayList<>(); + for (int iter = 0; iter < numOfItems1; iter++) { + String key = UUID.randomUUID().toString(); + if (iter > (onHeapCacheSize - 1)) { + // All these are bound to go to disk based cache. + diskTierKeys.add(key); + } else { + onHeapKeys.add(key); + } + TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); + spilloverStrategyService.computeIfAbsent(key, tieredCacheLoader); + } + assertEquals(numOfItems1, eventListener.enumMap.get(TierType.ON_HEAP).missCount.count()); + assertEquals(0, eventListener.enumMap.get(TierType.ON_HEAP).hitCount.count()); + assertTrue(eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count() > 0); + + assertEquals( + eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count(), + eventListener.enumMap.get(TierType.DISK).cachedCount.count() + ); + assertEquals(diskTierKeys.size(), eventListener.enumMap.get(TierType.DISK).cachedCount.count()); + + // Try to hit cache again with some randomization. + int numOfItems2 = randomIntBetween(50, 200); + int onHeapCacheHit = 0; + int diskCacheHit = 0; + int cacheMiss = 0; + for (int iter = 0; iter < numOfItems2; iter++) { + if (randomBoolean()) { + if (randomBoolean()) { // Hit cache with key stored in onHeap cache. + onHeapCacheHit++; + int index = randomIntBetween(0, onHeapKeys.size() - 1); + spilloverStrategyService.computeIfAbsent(onHeapKeys.get(index), getTieredCacheLoader()); + } else { // Hit cache with key stored in disk cache. + diskCacheHit++; + int index = randomIntBetween(0, diskTierKeys.size() - 1); + spilloverStrategyService.computeIfAbsent(diskTierKeys.get(index), getTieredCacheLoader()); + } + } else { + // Hit cache with randomized key which is expected to miss cache always. + TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); + spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); + cacheMiss++; + } + } + // On heap cache misses would also include diskCacheHits as it means it missed onHeap cache. + assertEquals(numOfItems1 + cacheMiss + diskCacheHit, eventListener.enumMap.get(TierType.ON_HEAP).missCount.count()); + assertEquals(onHeapCacheHit, eventListener.enumMap.get(TierType.ON_HEAP).hitCount.count()); + assertEquals(cacheMiss + numOfItems1, eventListener.enumMap.get(TierType.DISK).missCount.count()); + assertEquals(diskCacheHit, eventListener.enumMap.get(TierType.DISK).hitCount.count()); + } + + public void testComputeAndAbsentWithEvictionsFromBothTier() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(onHeapCacheSize + 1, 100); + int totalSize = onHeapCacheSize + diskCacheSize; + + MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); + TieredCacheSpilloverStrategyService spilloverStrategyService = intializeTieredCacheService( + onHeapCacheSize, + diskCacheSize, + eventListener + ); + + int numOfItems = randomIntBetween(totalSize + 1, totalSize * 3); + for (int iter = 0; iter < numOfItems; iter++) { + TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); + spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); + } + assertTrue(eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count() > 0); + assertTrue(eventListener.enumMap.get(TierType.DISK).evictionsMetric.count() > 0); + } + + public void testGetAndCount() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(onHeapCacheSize + 1, 100); + int totalSize = onHeapCacheSize + diskCacheSize; + + MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); + TieredCacheSpilloverStrategyService spilloverStrategyService = intializeTieredCacheService( + onHeapCacheSize, + diskCacheSize, + eventListener + ); + + int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); + List onHeapKeys = new ArrayList<>(); + List diskTierKeys = new ArrayList<>(); + for (int iter = 0; iter < numOfItems1; iter++) { + String key = UUID.randomUUID().toString(); + if (iter > (onHeapCacheSize - 1)) { + // All these are bound to go to disk based cache. + diskTierKeys.add(key); + } else { + onHeapKeys.add(key); + } + TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); + spilloverStrategyService.computeIfAbsent(key, tieredCacheLoader); + } + + for (int iter = 0; iter < numOfItems1; iter++) { + if (randomBoolean()) { + if (randomBoolean()) { + int index = randomIntBetween(0, onHeapKeys.size() - 1); + assertNotNull(spilloverStrategyService.get(onHeapKeys.get(index))); + } else { + int index = randomIntBetween(0, diskTierKeys.size() - 1); + assertNotNull(spilloverStrategyService.get(diskTierKeys.get(index))); + } + } else { + assertNull(spilloverStrategyService.get(UUID.randomUUID().toString())); + } + } + assertEquals(numOfItems1, spilloverStrategyService.count()); + } + + public void testWithDiskTierNull() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); + TieredCacheSpilloverStrategyService spilloverStrategyService = new TieredCacheSpilloverStrategyService.Builder< + String, + String>().setOnHeapCachingTier(new MockOnHeapCacheTier<>(onHeapCacheSize)).setTieredCacheEventListener(eventListener).build(); + int numOfItems = randomIntBetween(onHeapCacheSize + 1, onHeapCacheSize * 3); + for (int iter = 0; iter < numOfItems; iter++) { + TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); + spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); + } + assertTrue(eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count() > 0); + assertEquals(0, eventListener.enumMap.get(TierType.DISK).cachedCount.count()); + assertEquals(0, eventListener.enumMap.get(TierType.DISK).evictionsMetric.count()); + assertEquals(0, eventListener.enumMap.get(TierType.DISK).missCount.count()); + } + + private TieredCacheLoader getTieredCacheLoader() { + return new TieredCacheLoader() { + boolean isLoaded = false; + + @Override + public String load(String key) { + isLoaded = true; + return UUID.randomUUID().toString(); + } + + @Override + public boolean isLoaded() { + return isLoaded; + } + }; + } + + private TieredCacheSpilloverStrategyService intializeTieredCacheService( + int onHeapCacheSize, + int diksCacheSize, + TieredCacheEventListener cacheEventListener + ) { + DiskCachingTier diskCache = new MockDiskCachingTier<>(diksCacheSize); + OnHeapCachingTier openSearchOnHeapCache = new MockOnHeapCacheTier<>(onHeapCacheSize); + return new TieredCacheSpilloverStrategyService.Builder().setOnHeapCachingTier(openSearchOnHeapCache) + .setOnDiskCachingTier(diskCache) + .setTieredCacheEventListener(cacheEventListener) + .build(); + } + + class MockOnHeapCacheTier implements OnHeapCachingTier, RemovalListener { + + Map onHeapCacheTier; + int maxSize; + private RemovalListener removalListener; + + MockOnHeapCacheTier(int size) { + maxSize = size; + this.onHeapCacheTier = new ConcurrentHashMap(); + } + + @Override + public V get(K key) { + return this.onHeapCacheTier.get(key); + } + + @Override + public void put(K key, V value) { + this.onHeapCacheTier.put(key, value); + } + + @Override + public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { + if (this.onHeapCacheTier.size() > maxSize) { // If it exceeds, just notify for evict. + onRemoval(new RemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, TierType.ON_HEAP)); + return loader.load(key); + } + return this.onHeapCacheTier.computeIfAbsent(key, k -> { + try { + return loader.load(key); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public void invalidate(K key) { + this.onHeapCacheTier.remove(key); + } + + @Override + public V compute(K key, TieredCacheLoader loader) throws Exception { + if (this.onHeapCacheTier.size() >= maxSize) { // If it exceeds, just notify for evict. + onRemoval(new RemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, TierType.ON_HEAP)); + return loader.load(key); + } + return this.onHeapCacheTier.compute(key, ((k, v) -> { + try { + return loader.load(key); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + } + + @Override + public void setRemovalListener(RemovalListener removalListener) { + this.removalListener = removalListener; + } + + @Override + public void invalidateAll() { + this.onHeapCacheTier.clear(); + } + + @Override + public Iterable keys() { + return this.onHeapCacheTier.keySet(); + } + + @Override + public int count() { + return this.onHeapCacheTier.size(); + } + + @Override + public TierType getTierType() { + return TierType.ON_HEAP; + } + + @Override + public void onRemoval(RemovalNotification notification) { + removalListener.onRemoval(notification); + } + } + + class MockTieredCacheEventListener implements TieredCacheEventListener { + + EnumMap enumMap = new EnumMap<>(TierType.class); + + MockTieredCacheEventListener() { + for (TierType tierType : TierType.values()) { + enumMap.put(tierType, new TestStatsHolder()); + } + } + + @Override + public void onMiss(K key, TierType tierType) { + enumMap.get(tierType).missCount.inc(); + } + + @Override + public void onRemoval(RemovalNotification notification) { + if (notification.getRemovalReason().equals(RemovalReason.EVICTED)) { + enumMap.get(notification.getTierType()).evictionsMetric.inc(); + } + } + + @Override + public void onHit(K key, V value, TierType tierType) { + enumMap.get(tierType).hitCount.inc(); + } + + @Override + public void onCached(K key, V value, TierType tierType) { + enumMap.get(tierType).cachedCount.inc(); + } + + class TestStatsHolder { + final CounterMetric evictionsMetric = new CounterMetric(); + final CounterMetric hitCount = new CounterMetric(); + final CounterMetric missCount = new CounterMetric(); + + final CounterMetric cachedCount = new CounterMetric(); + } + } + + class MockDiskCachingTier implements DiskCachingTier, RemovalListener { + Map diskTier; + private RemovalListener removalListener; + int maxSize; + + MockDiskCachingTier(int size) { + this.maxSize = size; + diskTier = new ConcurrentHashMap(); + } + + @Override + public V get(K key) { + return this.diskTier.get(key); + } + + @Override + public void put(K key, V value) { + if (this.diskTier.size() >= maxSize) { // For simplification + onRemoval(new RemovalNotification<>(key, value, RemovalReason.EVICTED, TierType.DISK)); + return; + } + this.diskTier.put(key, value); + } + + @Override + public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { + return this.diskTier.computeIfAbsent(key, k -> { + try { + return loader.load(k); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public void invalidate(K key) { + this.diskTier.remove(key); + } + + @Override + public V compute(K key, TieredCacheLoader loader) throws Exception { + if (this.diskTier.size() >= maxSize) { // If it exceeds, just notify for evict. + onRemoval(new RemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, TierType.DISK)); + return loader.load(key); + } + return this.diskTier.compute(key, (k, v) -> { + try { + return loader.load(key); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public void setRemovalListener(RemovalListener removalListener) { + this.removalListener = removalListener; + } + + @Override + public void invalidateAll() { + this.diskTier.clear(); + } + + @Override + public Iterable keys() { + return null; + } + + @Override + public int count() { + return this.diskTier.size(); + } + + @Override + public TierType getTierType() { + return TierType.DISK; + } + + @Override + public void onRemoval(RemovalNotification notification) { + this.removalListener.onRemoval(notification); + } + } +} From 168076fb5cbcc926cfa9c8aac6da82e8d31d1bea Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 26 Oct 2023 14:42:08 -0700 Subject: [PATCH 02/19] Added javadoc for new files/packages Signed-off-by: Sagar Upadhyaya --- .../common/cache/RemovalNotification.java | 2 +- .../cache/tier}/CachingTier.java | 2 +- .../cache/tier}/DiskCachingTier.java | 2 +- .../cache/tier}/OnHeapCachingTier.java | 2 +- .../cache/tier}/OpenSearchOnHeapCache.java | 26 ++++++--- .../cache/tier}/TierType.java | 5 +- .../cache/tier}/TieredCacheEventListener.java | 7 ++- .../cache/tier}/TieredCacheLoader.java | 7 ++- .../cache/tier}/TieredCacheService.java | 2 +- .../TieredCacheSpilloverStrategyService.java | 25 ++++---- .../common/cache/tier/package-info.java | 10 ++++ .../cache/request/ShardRequestCache.java | 2 +- .../AbstractIndexShardCacheEntity.java | 1 + .../indices/DummyDiskCachingTier.java | 58 ------------------- .../indices/IndicesRequestCache.java | 9 ++- ...redCacheSpilloverStrategyServiceTests.java | 2 +- .../indices/IndicesServiceCloseTests.java | 1 + 17 files changed, 73 insertions(+), 90 deletions(-) rename server/src/main/java/org/opensearch/{indices => common/cache/tier}/CachingTier.java (96%) rename server/src/main/java/org/opensearch/{indices => common/cache/tier}/DiskCachingTier.java (91%) rename server/src/main/java/org/opensearch/{indices => common/cache/tier}/OnHeapCachingTier.java (91%) rename server/src/main/java/org/opensearch/{indices => common/cache/tier}/OpenSearchOnHeapCache.java (80%) rename server/src/main/java/org/opensearch/{indices => common/cache/tier}/TierType.java (77%) rename server/src/main/java/org/opensearch/{indices => common/cache/tier}/TieredCacheEventListener.java (76%) rename server/src/main/java/org/opensearch/{indices => common/cache/tier}/TieredCacheLoader.java (66%) rename server/src/main/java/org/opensearch/{indices => common/cache/tier}/TieredCacheService.java (94%) rename server/src/main/java/org/opensearch/{indices => common/cache/tier}/TieredCacheSpilloverStrategyService.java (92%) create mode 100644 server/src/main/java/org/opensearch/common/cache/tier/package-info.java delete mode 100644 server/src/main/java/org/opensearch/indices/DummyDiskCachingTier.java rename server/src/test/java/org/opensearch/{indices => common/cache/tier}/TieredCacheSpilloverStrategyServiceTests.java (99%) diff --git a/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java b/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java index 71e240064c6ae..2152c4917b62d 100644 --- a/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java +++ b/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java @@ -32,7 +32,7 @@ package org.opensearch.common.cache; -import org.opensearch.indices.TierType; +import org.opensearch.common.cache.tier.TierType; /** * Notification when an element is removed from the cache diff --git a/server/src/main/java/org/opensearch/indices/CachingTier.java b/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java similarity index 96% rename from server/src/main/java/org/opensearch/indices/CachingTier.java rename to server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java index 7bf7294467114..48fd5deadc111 100644 --- a/server/src/main/java/org/opensearch/indices/CachingTier.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; import org.opensearch.common.cache.RemovalListener; diff --git a/server/src/main/java/org/opensearch/indices/DiskCachingTier.java b/server/src/main/java/org/opensearch/common/cache/tier/DiskCachingTier.java similarity index 91% rename from server/src/main/java/org/opensearch/indices/DiskCachingTier.java rename to server/src/main/java/org/opensearch/common/cache/tier/DiskCachingTier.java index abc8952c0b8e2..4db71b6378a02 100644 --- a/server/src/main/java/org/opensearch/indices/DiskCachingTier.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/DiskCachingTier.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; /** * This is specific to disk caching tier and can be used to add methods which are specific to disk tier. diff --git a/server/src/main/java/org/opensearch/indices/OnHeapCachingTier.java b/server/src/main/java/org/opensearch/common/cache/tier/OnHeapCachingTier.java similarity index 91% rename from server/src/main/java/org/opensearch/indices/OnHeapCachingTier.java rename to server/src/main/java/org/opensearch/common/cache/tier/OnHeapCachingTier.java index f7d68c27c1904..127fa6ee8e6b3 100644 --- a/server/src/main/java/org/opensearch/indices/OnHeapCachingTier.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/OnHeapCachingTier.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; /** * This is specific to onHeap caching tier and can be used to add methods which are specific to this tier. diff --git a/server/src/main/java/org/opensearch/indices/OpenSearchOnHeapCache.java b/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java similarity index 80% rename from server/src/main/java/org/opensearch/indices/OpenSearchOnHeapCache.java rename to server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java index 189b741ed1696..22d2f769507cf 100644 --- a/server/src/main/java/org/opensearch/indices/OpenSearchOnHeapCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; import org.opensearch.common.cache.Cache; import org.opensearch.common.cache.CacheBuilder; @@ -14,21 +14,28 @@ import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.unit.TimeValue; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.function.ToLongBiFunction; +/** + * This variant of on-heap cache uses OpenSearch custom cache implementation. + * @param Type of key + * @param Type of value + */ public class OpenSearchOnHeapCache implements OnHeapCachingTier, RemovalListener { private final Cache cache; private RemovalListener removalListener; - private OpenSearchOnHeapCache(long maxWeightInBytes, ToLongBiFunction weigher, TimeValue expireAfterAcess) { + private OpenSearchOnHeapCache(Builder builder) { + Objects.requireNonNull(builder.weigher); CacheBuilder cacheBuilder = CacheBuilder.builder() - .setMaximumWeight(maxWeightInBytes) - .weigher(weigher) + .setMaximumWeight(builder.maxWeightInBytes) + .weigher(builder.weigher) .removalListener(this); - if (expireAfterAcess != null) { - cacheBuilder.setExpireAfterAccess(expireAfterAcess); + if (builder.expireAfterAcess != null) { + cacheBuilder.setExpireAfterAccess(builder.expireAfterAcess); } cache = cacheBuilder.build(); } @@ -93,6 +100,11 @@ public void onRemoval(RemovalNotification notification) { removalListener.onRemoval(notification); } + /** + * Builder object + * @param Type of key + * @param Type of value + */ public static class Builder { private long maxWeightInBytes; @@ -118,7 +130,7 @@ public Builder setExpireAfterAccess(TimeValue expireAfterAcess) { } public OpenSearchOnHeapCache build() { - return new OpenSearchOnHeapCache(maxWeightInBytes, weigher, expireAfterAcess); + return new OpenSearchOnHeapCache(this); } } } diff --git a/server/src/main/java/org/opensearch/indices/TierType.java b/server/src/main/java/org/opensearch/common/cache/tier/TierType.java similarity index 77% rename from server/src/main/java/org/opensearch/indices/TierType.java rename to server/src/main/java/org/opensearch/common/cache/tier/TierType.java index 9a286fd26151b..ca61b636c1dda 100644 --- a/server/src/main/java/org/opensearch/indices/TierType.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TierType.java @@ -6,8 +6,11 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; +/** + * Tier types in cache. + */ public enum TierType { ON_HEAP, diff --git a/server/src/main/java/org/opensearch/indices/TieredCacheEventListener.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheEventListener.java similarity index 76% rename from server/src/main/java/org/opensearch/indices/TieredCacheEventListener.java rename to server/src/main/java/org/opensearch/common/cache/tier/TieredCacheEventListener.java index 084ac5a57e0d3..05b59bf16b282 100644 --- a/server/src/main/java/org/opensearch/indices/TieredCacheEventListener.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheEventListener.java @@ -6,10 +6,15 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; import org.opensearch.common.cache.RemovalNotification; +/** + * This can be used to listen to tiered caching events + * @param Type of key + * @param Type of value + */ public interface TieredCacheEventListener { void onMiss(K key, TierType tierType); diff --git a/server/src/main/java/org/opensearch/indices/TieredCacheLoader.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheLoader.java similarity index 66% rename from server/src/main/java/org/opensearch/indices/TieredCacheLoader.java rename to server/src/main/java/org/opensearch/common/cache/tier/TieredCacheLoader.java index f6bb1a74e973e..d720feade0609 100644 --- a/server/src/main/java/org/opensearch/indices/TieredCacheLoader.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheLoader.java @@ -6,8 +6,13 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; +/** + * Used to load value in tiered cache if not present. + * @param Type of key + * @param Type of value + */ public interface TieredCacheLoader { V load(K key) throws Exception; diff --git a/server/src/main/java/org/opensearch/indices/TieredCacheService.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheService.java similarity index 94% rename from server/src/main/java/org/opensearch/indices/TieredCacheService.java rename to server/src/main/java/org/opensearch/common/cache/tier/TieredCacheService.java index 59e5e0e00b6c1..31d58510206f0 100644 --- a/server/src/main/java/org/opensearch/indices/TieredCacheService.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheService.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; import java.util.Optional; diff --git a/server/src/main/java/org/opensearch/indices/TieredCacheSpilloverStrategyService.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java similarity index 92% rename from server/src/main/java/org/opensearch/indices/TieredCacheSpilloverStrategyService.java rename to server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java index 7799170a1ede9..c148145274546 100644 --- a/server/src/main/java/org/opensearch/indices/TieredCacheSpilloverStrategyService.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; import org.opensearch.common.cache.RemovalListener; import org.opensearch.common.cache.RemovalNotification; @@ -39,14 +39,10 @@ public class TieredCacheSpilloverStrategyService implements TieredCacheSer */ private final List> cachingTierList; - private TieredCacheSpilloverStrategyService( - OnHeapCachingTier onHeapCachingTier, - DiskCachingTier diskCachingTier, - TieredCacheEventListener tieredCacheEventListener - ) { - this.onHeapCachingTier = Objects.requireNonNull(onHeapCachingTier); - this.diskCachingTier = Optional.ofNullable(diskCachingTier); - this.tieredCacheEventListener = Objects.requireNonNull(tieredCacheEventListener); + private TieredCacheSpilloverStrategyService(Builder builder) { + this.onHeapCachingTier = Objects.requireNonNull(builder.onHeapCachingTier); + this.diskCachingTier = Optional.ofNullable(builder.diskCachingTier); + this.tieredCacheEventListener = Objects.requireNonNull(builder.tieredCacheEventListener); this.cachingTierList = this.diskCachingTier.map(diskTier -> Arrays.asList(onHeapCachingTier, diskTier)) .orElse(List.of(onHeapCachingTier)); setRemovalListeners(); @@ -197,6 +193,11 @@ public static class CacheValue { } } + /** + * Builder object + * @param Type of key + * @param Type of value + */ public static class Builder { private OnHeapCachingTier onHeapCachingTier; private DiskCachingTier diskCachingTier; @@ -220,11 +221,7 @@ public Builder setTieredCacheEventListener(TieredCacheEventListener } public TieredCacheSpilloverStrategyService build() { - return new TieredCacheSpilloverStrategyService( - this.onHeapCachingTier, - this.diskCachingTier, - this.tieredCacheEventListener - ); + return new TieredCacheSpilloverStrategyService(this); } } diff --git a/server/src/main/java/org/opensearch/common/cache/tier/package-info.java b/server/src/main/java/org/opensearch/common/cache/tier/package-info.java new file mode 100644 index 0000000000000..7ad81dbe3073c --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/tier/package-info.java @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** Base package for cache tier support. */ +package org.opensearch.common.cache.tier; diff --git a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java index 1beef5217355f..efad437804bef 100644 --- a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java +++ b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java @@ -33,9 +33,9 @@ package org.opensearch.index.cache.request; import org.apache.lucene.util.Accountable; +import org.opensearch.common.cache.tier.TierType; import org.opensearch.common.metrics.CounterMetric; import org.opensearch.core.common.bytes.BytesReference; -import org.opensearch.indices.TierType; import java.util.EnumMap; diff --git a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java index 2eef16df2bb9a..d9c256b4b4a94 100644 --- a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java +++ b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java @@ -34,6 +34,7 @@ import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.cache.RemovalReason; +import org.opensearch.common.cache.tier.TierType; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.index.cache.request.ShardRequestCache; import org.opensearch.index.shard.IndexShard; diff --git a/server/src/main/java/org/opensearch/indices/DummyDiskCachingTier.java b/server/src/main/java/org/opensearch/indices/DummyDiskCachingTier.java deleted file mode 100644 index 26a78b6c61920..0000000000000 --- a/server/src/main/java/org/opensearch/indices/DummyDiskCachingTier.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.indices; - -import org.opensearch.common.cache.RemovalListener; - -import java.util.Collections; - -public class DummyDiskCachingTier implements CachingTier { - - @Override - public V get(K key) { - return null; - } - - @Override - public void put(K key, V value) {} - - @Override - public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { - return null; - } - - @Override - public void invalidate(K key) {} - - @Override - public V compute(K key, TieredCacheLoader loader) throws Exception { - return null; - } - - @Override - public void setRemovalListener(RemovalListener removalListener) {} - - @Override - public void invalidateAll() {} - - @Override - public Iterable keys() { - return Collections::emptyIterator; - } - - @Override - public int count() { - return 0; - } - - @Override - public TierType getTierType() { - return TierType.DISK; - } -} diff --git a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java index 6781d3fa0eb2a..c5dcaa7f28811 100644 --- a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java +++ b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java @@ -40,6 +40,13 @@ import org.apache.lucene.util.RamUsageEstimator; import org.opensearch.common.CheckedSupplier; import org.opensearch.common.cache.RemovalNotification; +import org.opensearch.common.cache.tier.OnHeapCachingTier; +import org.opensearch.common.cache.tier.OpenSearchOnHeapCache; +import org.opensearch.common.cache.tier.TierType; +import org.opensearch.common.cache.tier.TieredCacheEventListener; +import org.opensearch.common.cache.tier.TieredCacheLoader; +import org.opensearch.common.cache.tier.TieredCacheService; +import org.opensearch.common.cache.tier.TieredCacheSpilloverStrategyService; import org.opensearch.common.lucene.index.OpenSearchDirectoryReader; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Setting.Property; @@ -190,7 +197,7 @@ void invalidate(CacheEntity cacheEntity, DirectoryReader reader, BytesReference * * @opensearch.internal */ - private static class Loader implements org.opensearch.indices.TieredCacheLoader { + private static class Loader implements TieredCacheLoader { private final CacheEntity entity; private final CheckedSupplier loader; diff --git a/server/src/test/java/org/opensearch/indices/TieredCacheSpilloverStrategyServiceTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java similarity index 99% rename from server/src/test/java/org/opensearch/indices/TieredCacheSpilloverStrategyServiceTests.java rename to server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java index 4c4c7f195ba31..3cd08df649f72 100644 --- a/server/src/test/java/org/opensearch/indices/TieredCacheSpilloverStrategyServiceTests.java +++ b/server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.indices; +package org.opensearch.common.cache.tier; import org.opensearch.common.cache.RemovalListener; import org.opensearch.common.cache.RemovalNotification; diff --git a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java index 2fe1f87f5d828..38ff7ba8cef21 100644 --- a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java +++ b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java @@ -37,6 +37,7 @@ import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.routing.allocation.DiskThresholdSettings; import org.opensearch.common.cache.RemovalNotification; +import org.opensearch.common.cache.tier.TierType; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.OpenSearchExecutors; import org.opensearch.core.common.bytes.BytesArray; From 8fed7c732366cf6507e95fcf7b842b468b16aa3c Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 26 Oct 2023 14:46:09 -0700 Subject: [PATCH 03/19] Added changelog Signed-off-by: Sagar Upadhyaya --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b40878066960a..fd5d20bef363a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [Remote cluster state] Make index and global metadata upload timeout dynamic cluster settings ([#10814](https://github.com/opensearch-project/OpenSearch/pull/10814)) - Added cluster setting cluster.restrict.index.replication_type to restrict setting of index setting replication type ([#10866](https://github.com/opensearch-project/OpenSearch/pull/10866)) - Add cluster state stats ([#10670](https://github.com/opensearch-project/OpenSearch/pull/10670)) +- [Tiered caching] Framework changes ([#10753](https://github.com/opensearch-project/OpenSearch/pull/10753) ### Dependencies - Bump `com.google.api.grpc:proto-google-common-protos` from 2.10.0 to 2.25.1 ([#10208](https://github.com/opensearch-project/OpenSearch/pull/10208), [#10298](https://github.com/opensearch-project/OpenSearch/pull/10298)) From 5ace3a7315caa4de92173fc26296bc787c64595a Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 26 Oct 2023 16:30:39 -0700 Subject: [PATCH 04/19] Fixing javadoc warnings Signed-off-by: Sagar Upadhyaya --- .../cache/tier/TieredCacheSpilloverStrategyService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java index c148145274546..153dbf9b330f5 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java @@ -55,7 +55,7 @@ private TieredCacheSpilloverStrategyService(Builder builder) { * @param key Key for lookup. * @param loader Used to load value in case it is not present in any tier. * @return value - * @throws Exception + * @throws Exception exception thrown */ @Override public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { @@ -80,7 +80,7 @@ public V get(K key) { /** * First fetches the tier type which has this key. And then invalidate accordingly. - * @param key + * @param key key to invalidate */ @Override public void invalidate(K key) { From 2e9c47836bf887c02908d3eba1e5ec87f8311bc3 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Wed, 22 Nov 2023 05:52:54 -0800 Subject: [PATCH 05/19] Addressing comments Signed-off-by: Sagar Upadhyaya --- CHANGELOG.md | 2 +- .../common/cache/RemovalNotification.java | 12 --- .../common/cache/tier/CachingTier.java | 7 +- .../cache/tier/OpenSearchOnHeapCache.java | 13 ++- .../cache/tier/TieredCacheEventListener.java | 27 ----- .../tier/TieredCacheRemovalNotification.java | 38 +++++++ .../CacheStoreType.java} | 6 +- .../listeners/TieredCacheEventListener.java | 28 +++++ .../listeners/TieredCacheRemovalListener.java | 21 ++++ .../{ => service}/TieredCacheService.java | 6 +- .../TieredCacheSpilloverStrategyService.java | 24 +++-- .../cache/request/ShardRequestCache.java | 38 ++++--- .../AbstractIndexShardCacheEntity.java | 35 +++--- .../indices/IndicesRequestCache.java | 29 ++--- ...redCacheSpilloverStrategyServiceTests.java | 102 +++++++++--------- .../indices/IndicesServiceCloseTests.java | 8 +- 16 files changed, 237 insertions(+), 159 deletions(-) delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/TieredCacheEventListener.java create mode 100644 server/src/main/java/org/opensearch/common/cache/tier/TieredCacheRemovalNotification.java rename server/src/main/java/org/opensearch/common/cache/tier/{TierType.java => enums/CacheStoreType.java} (67%) create mode 100644 server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheEventListener.java create mode 100644 server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheRemovalListener.java rename server/src/main/java/org/opensearch/common/cache/tier/{ => service}/TieredCacheService.java (77%) rename server/src/main/java/org/opensearch/common/cache/tier/{ => service}/TieredCacheSpilloverStrategyService.java (88%) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd5d20bef363a..d67df86856ff4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,7 +98,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [Remote cluster state] Make index and global metadata upload timeout dynamic cluster settings ([#10814](https://github.com/opensearch-project/OpenSearch/pull/10814)) - Added cluster setting cluster.restrict.index.replication_type to restrict setting of index setting replication type ([#10866](https://github.com/opensearch-project/OpenSearch/pull/10866)) - Add cluster state stats ([#10670](https://github.com/opensearch-project/OpenSearch/pull/10670)) -- [Tiered caching] Framework changes ([#10753](https://github.com/opensearch-project/OpenSearch/pull/10753) +- [Tiered caching] Defining interfaces, service and extending IndicesRequestCache with basic Tier support ([#10753] (https://github.com/opensearch-project/OpenSearch/pull/10753)) ### Dependencies - Bump `com.google.api.grpc:proto-google-common-protos` from 2.10.0 to 2.25.1 ([#10208](https://github.com/opensearch-project/OpenSearch/pull/10208), [#10298](https://github.com/opensearch-project/OpenSearch/pull/10298)) diff --git a/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java b/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java index 2152c4917b62d..6d355b2122460 100644 --- a/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java +++ b/server/src/main/java/org/opensearch/common/cache/RemovalNotification.java @@ -32,8 +32,6 @@ package org.opensearch.common.cache; -import org.opensearch.common.cache.tier.TierType; - /** * Notification when an element is removed from the cache * @@ -44,17 +42,11 @@ public class RemovalNotification { private final K key; private final V value; private final RemovalReason removalReason; - private final TierType tierType; public RemovalNotification(K key, V value, RemovalReason removalReason) { - this(key, value, removalReason, TierType.ON_HEAP); - } - - public RemovalNotification(K key, V value, RemovalReason removalReason, TierType tierType) { this.key = key; this.value = value; this.removalReason = removalReason; - this.tierType = tierType; } public K getKey() { @@ -68,8 +60,4 @@ public V getValue() { public RemovalReason getRemovalReason() { return removalReason; } - - public TierType getTierType() { - return tierType; - } } diff --git a/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java b/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java index 48fd5deadc111..11cbc562d5941 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java @@ -8,7 +8,8 @@ package org.opensearch.common.cache.tier; -import org.opensearch.common.cache.RemovalListener; +import org.opensearch.common.cache.tier.enums.CacheStoreType; +import org.opensearch.common.cache.tier.listeners.TieredCacheRemovalListener; /** * Caching tier interface. Can be implemented/extended by concrete classes to provide different flavors of cache like @@ -28,7 +29,7 @@ public interface CachingTier { V compute(K key, TieredCacheLoader loader) throws Exception; - void setRemovalListener(RemovalListener removalListener); + void setRemovalListener(TieredCacheRemovalListener removalListener); void invalidateAll(); @@ -36,7 +37,7 @@ public interface CachingTier { int count(); - TierType getTierType(); + CacheStoreType getTierType(); /** * Force any outstanding size-based and time-based evictions to occur diff --git a/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java b/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java index 22d2f769507cf..91720dd3ecabc 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java @@ -12,6 +12,8 @@ import org.opensearch.common.cache.CacheBuilder; import org.opensearch.common.cache.RemovalListener; import org.opensearch.common.cache.RemovalNotification; +import org.opensearch.common.cache.tier.enums.CacheStoreType; +import org.opensearch.common.cache.tier.listeners.TieredCacheRemovalListener; import org.opensearch.common.unit.TimeValue; import java.util.Objects; @@ -26,7 +28,7 @@ public class OpenSearchOnHeapCache implements OnHeapCachingTier, RemovalListener { private final Cache cache; - private RemovalListener removalListener; + private TieredCacheRemovalListener removalListener; private OpenSearchOnHeapCache(Builder builder) { Objects.requireNonNull(builder.weigher); @@ -41,7 +43,7 @@ private OpenSearchOnHeapCache(Builder builder) { } @Override - public void setRemovalListener(RemovalListener removalListener) { + public void setRemovalListener(TieredCacheRemovalListener removalListener) { this.removalListener = removalListener; } @@ -61,8 +63,8 @@ public int count() { } @Override - public TierType getTierType() { - return TierType.ON_HEAP; + public CacheStoreType getTierType() { + return CacheStoreType.ON_HEAP; } @Override @@ -97,7 +99,8 @@ public void refresh() { @Override public void onRemoval(RemovalNotification notification) { - removalListener.onRemoval(notification); + removalListener.onRemoval(new TieredCacheRemovalNotification<>(notification.getKey(), notification.getValue() + , notification.getRemovalReason(), CacheStoreType.ON_HEAP)); } /** diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheEventListener.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheEventListener.java deleted file mode 100644 index 05b59bf16b282..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheEventListener.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier; - -import org.opensearch.common.cache.RemovalNotification; - -/** - * This can be used to listen to tiered caching events - * @param Type of key - * @param Type of value - */ -public interface TieredCacheEventListener { - - void onMiss(K key, TierType tierType); - - void onRemoval(RemovalNotification notification); - - void onHit(K key, V value, TierType tierType); - - void onCached(K key, V value, TierType tierType); -} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheRemovalNotification.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheRemovalNotification.java new file mode 100644 index 0000000000000..d72279144b6ef --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheRemovalNotification.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.tier; + +import org.opensearch.common.cache.RemovalNotification; +import org.opensearch.common.cache.RemovalReason; +import org.opensearch.common.cache.tier.enums.CacheStoreType; + +/** + * Notification when an element is removed from tiered cache. + * @param Type of key + * @param Type of value + * + * @opensearch.internal + */ +public class TieredCacheRemovalNotification extends RemovalNotification { + private final CacheStoreType cacheStoreType; + + public TieredCacheRemovalNotification(K key, V value, RemovalReason removalReason) { + super(key, value, removalReason); + this.cacheStoreType = CacheStoreType.ON_HEAP; + } + + public TieredCacheRemovalNotification(K key, V value, RemovalReason removalReason, CacheStoreType cacheStoreType) { + super(key, value, removalReason); + this.cacheStoreType = cacheStoreType; + } + + public CacheStoreType getTierType() { + return cacheStoreType; + } +} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TierType.java b/server/src/main/java/org/opensearch/common/cache/tier/enums/CacheStoreType.java similarity index 67% rename from server/src/main/java/org/opensearch/common/cache/tier/TierType.java rename to server/src/main/java/org/opensearch/common/cache/tier/enums/CacheStoreType.java index ca61b636c1dda..80d67f0bb5bcf 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TierType.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/enums/CacheStoreType.java @@ -6,12 +6,12 @@ * compatible open source license. */ -package org.opensearch.common.cache.tier; +package org.opensearch.common.cache.tier.enums; /** - * Tier types in cache. + * Cache store types in tiered cache. */ -public enum TierType { +public enum CacheStoreType { ON_HEAP, DISK; diff --git a/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheEventListener.java b/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheEventListener.java new file mode 100644 index 0000000000000..03489772f17e2 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheEventListener.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.tier.listeners; + +import org.opensearch.common.cache.tier.enums.CacheStoreType; +import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; + +/** + * This can be used to listen to tiered caching events + * @param Type of key + * @param Type of value + */ +public interface TieredCacheEventListener { + + void onMiss(K key, CacheStoreType cacheStoreType); + + void onRemoval(TieredCacheRemovalNotification notification); + + void onHit(K key, V value, CacheStoreType cacheStoreType); + + void onCached(K key, V value, CacheStoreType cacheStoreType); +} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheRemovalListener.java b/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheRemovalListener.java new file mode 100644 index 0000000000000..4fcbae7432b64 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheRemovalListener.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.tier.listeners; + +import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; + +/** + * Listener for removing an element from tiered cache + * + * @opensearch.internal + */ +@FunctionalInterface +public interface TieredCacheRemovalListener { + void onRemoval(TieredCacheRemovalNotification notification); +} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheService.java b/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheService.java similarity index 77% rename from server/src/main/java/org/opensearch/common/cache/tier/TieredCacheService.java rename to server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheService.java index 31d58510206f0..91896b7cfa8e5 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheService.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheService.java @@ -6,7 +6,11 @@ * compatible open source license. */ -package org.opensearch.common.cache.tier; +package org.opensearch.common.cache.tier.service; + +import org.opensearch.common.cache.tier.DiskCachingTier; +import org.opensearch.common.cache.tier.OnHeapCachingTier; +import org.opensearch.common.cache.tier.TieredCacheLoader; import java.util.Optional; diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java b/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java similarity index 88% rename from server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java rename to server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java index 153dbf9b330f5..269ee24e364c0 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyService.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java @@ -6,11 +6,17 @@ * compatible open source license. */ -package org.opensearch.common.cache.tier; +package org.opensearch.common.cache.tier.service; -import org.opensearch.common.cache.RemovalListener; -import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.cache.RemovalReason; +import org.opensearch.common.cache.tier.enums.CacheStoreType; +import org.opensearch.common.cache.tier.CachingTier; +import org.opensearch.common.cache.tier.DiskCachingTier; +import org.opensearch.common.cache.tier.OnHeapCachingTier; +import org.opensearch.common.cache.tier.TieredCacheLoader; +import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; +import org.opensearch.common.cache.tier.listeners.TieredCacheEventListener; +import org.opensearch.common.cache.tier.listeners.TieredCacheRemovalListener; import java.util.Arrays; import java.util.List; @@ -24,7 +30,7 @@ * @param Type of key * @param Type of value */ -public class TieredCacheSpilloverStrategyService implements TieredCacheService, RemovalListener { +public class TieredCacheSpilloverStrategyService implements TieredCacheService, TieredCacheRemovalListener { private final OnHeapCachingTier onHeapCachingTier; @@ -63,7 +69,7 @@ public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception if (cacheValue == null) { // Add the value to the onHeap cache. Any items if evicted will be moved to lower tier. V value = onHeapCachingTier.compute(key, loader); - tieredCacheEventListener.onCached(key, value, TierType.ON_HEAP); + tieredCacheEventListener.onCached(key, value, CacheStoreType.ON_HEAP); return value; } return cacheValue.value; @@ -126,13 +132,13 @@ public long count() { * @param notification Contains info about the removal like reason, key/value etc. */ @Override - public void onRemoval(RemovalNotification notification) { + public void onRemoval(TieredCacheRemovalNotification notification) { if (RemovalReason.EVICTED.equals(notification.getRemovalReason())) { switch (notification.getTierType()) { case ON_HEAP: diskCachingTier.ifPresent(diskTier -> { diskTier.put(notification.getKey(), notification.getValue()); - tieredCacheEventListener.onCached(notification.getKey(), notification.getValue(), TierType.DISK); + tieredCacheEventListener.onCached(notification.getKey(), notification.getValue(), CacheStoreType.DISK); }); break; default: @@ -185,9 +191,9 @@ private Function> getValueFromTierCache(boolean trackStats) { */ public static class CacheValue { V value; - TierType source; + CacheStoreType source; - CacheValue(V value, TierType source) { + CacheValue(V value, CacheStoreType source) { this.value = value; this.source = source; } diff --git a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java index efad437804bef..5aa0dd051578d 100644 --- a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java +++ b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java @@ -33,7 +33,7 @@ package org.opensearch.index.cache.request; import org.apache.lucene.util.Accountable; -import org.opensearch.common.cache.tier.TierType; +import org.opensearch.common.cache.tier.enums.CacheStoreType; import org.opensearch.common.metrics.CounterMetric; import org.opensearch.core.common.bytes.BytesReference; @@ -46,39 +46,43 @@ */ public final class ShardRequestCache { - private EnumMap statsHolder = new EnumMap<>(TierType.class); + private EnumMap statsHolder = new EnumMap<>(CacheStoreType.class); public ShardRequestCache() { - for (TierType tierType : TierType.values()) { - statsHolder.put(tierType, new StatsHolder()); + for (CacheStoreType cacheStoreType : CacheStoreType.values()) { + statsHolder.put(cacheStoreType, new StatsHolder()); } } public RequestCacheStats stats() { // TODO: Change RequestCacheStats to support disk tier stats. return new RequestCacheStats( - statsHolder.get(TierType.ON_HEAP).totalMetric.count(), - statsHolder.get(TierType.ON_HEAP).evictionsMetric.count(), - statsHolder.get(TierType.ON_HEAP).hitCount.count(), - statsHolder.get(TierType.ON_HEAP).missCount.count() + statsHolder.get(CacheStoreType.ON_HEAP).totalMetric.count(), + statsHolder.get(CacheStoreType.ON_HEAP).evictionsMetric.count(), + statsHolder.get(CacheStoreType.ON_HEAP).hitCount.count(), + statsHolder.get(CacheStoreType.ON_HEAP).missCount.count() ); } - public void onHit(TierType tierType) { - statsHolder.get(tierType).hitCount.inc(); + public void onHit(CacheStoreType cacheStoreType) { + statsHolder.get(cacheStoreType).hitCount.inc(); } - public void onMiss(TierType tierType) { - statsHolder.get(tierType).missCount.inc(); + public void onMiss(CacheStoreType cacheStoreType) { + statsHolder.get(cacheStoreType).missCount.inc(); } - public void onCached(Accountable key, BytesReference value, TierType tierType) { - statsHolder.get(tierType).totalMetric.inc(key.ramBytesUsed() + value.ramBytesUsed()); + public void onCached(Accountable key, BytesReference value, CacheStoreType cacheStoreType) { + statsHolder.get(cacheStoreType).totalMetric.inc(key.ramBytesUsed() + value.ramBytesUsed()); } - public void onRemoval(Accountable key, BytesReference value, boolean evicted, TierType tierType) { + public void onRemoval(Accountable key, BytesReference value, boolean evicted) { + onRemoval(key, value, evicted, CacheStoreType.ON_HEAP); // By default On heap cache. + } + + public void onRemoval(Accountable key, BytesReference value, boolean evicted, CacheStoreType cacheStoreType) { if (evicted) { - statsHolder.get(tierType).evictionsMetric.inc(); + statsHolder.get(cacheStoreType).evictionsMetric.inc(); } long dec = 0; if (key != null) { @@ -87,7 +91,7 @@ public void onRemoval(Accountable key, BytesReference value, boolean evicted, Ti if (value != null) { dec += value.ramBytesUsed(); } - statsHolder.get(tierType).totalMetric.dec(dec); + statsHolder.get(cacheStoreType).totalMetric.dec(dec); } static class StatsHolder { diff --git a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java index d9c256b4b4a94..5beb4697190e2 100644 --- a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java +++ b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java @@ -34,7 +34,8 @@ import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.cache.RemovalReason; -import org.opensearch.common.cache.tier.TierType; +import org.opensearch.common.cache.tier.enums.CacheStoreType; +import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.index.cache.request.ShardRequestCache; import org.opensearch.index.shard.IndexShard; @@ -52,27 +53,35 @@ abstract class AbstractIndexShardCacheEntity implements IndicesRequestCache.Cach protected abstract ShardRequestCache stats(); @Override - public final void onCached(IndicesRequestCache.Key key, BytesReference value, TierType tierType) { - stats().onCached(key, value, tierType); + public final void onCached(IndicesRequestCache.Key key, BytesReference value, CacheStoreType cacheStoreType) { + stats().onCached(key, value, cacheStoreType); } @Override - public final void onHit(TierType tierType) { - stats().onHit(tierType); + public final void onHit(CacheStoreType cacheStoreType) { + stats().onHit(cacheStoreType); } @Override - public final void onMiss(TierType tierType) { - stats().onMiss(tierType); + public final void onMiss(CacheStoreType cacheStoreType) { + stats().onMiss(cacheStoreType); } @Override public final void onRemoval(RemovalNotification notification) { - stats().onRemoval( - notification.getKey(), - notification.getValue(), - notification.getRemovalReason() == RemovalReason.EVICTED, - notification.getTierType() - ); + if (notification instanceof TieredCacheRemovalNotification){ + stats().onRemoval( + notification.getKey(), + notification.getValue(), + notification.getRemovalReason() == RemovalReason.EVICTED, + ((TieredCacheRemovalNotification) notification).getTierType() + ); + } else { + stats().onRemoval( + notification.getKey(), + notification.getValue(), + notification.getRemovalReason() == RemovalReason.EVICTED + ); + } } } diff --git a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java index c5dcaa7f28811..d7462e8dc3287 100644 --- a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java +++ b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java @@ -42,11 +42,12 @@ import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.cache.tier.OnHeapCachingTier; import org.opensearch.common.cache.tier.OpenSearchOnHeapCache; -import org.opensearch.common.cache.tier.TierType; -import org.opensearch.common.cache.tier.TieredCacheEventListener; +import org.opensearch.common.cache.tier.enums.CacheStoreType; +import org.opensearch.common.cache.tier.listeners.TieredCacheEventListener; import org.opensearch.common.cache.tier.TieredCacheLoader; -import org.opensearch.common.cache.tier.TieredCacheService; -import org.opensearch.common.cache.tier.TieredCacheSpilloverStrategyService; +import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; +import org.opensearch.common.cache.tier.service.TieredCacheService; +import org.opensearch.common.cache.tier.service.TieredCacheSpilloverStrategyService; import org.opensearch.common.lucene.index.OpenSearchDirectoryReader; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Setting.Property; @@ -139,23 +140,23 @@ void clear(CacheEntity entity) { } @Override - public void onMiss(Key key, TierType tierType) { - key.entity.onMiss(tierType); + public void onMiss(Key key, CacheStoreType cacheStoreType) { + key.entity.onMiss(cacheStoreType); } @Override - public void onRemoval(RemovalNotification notification) { + public void onRemoval(TieredCacheRemovalNotification notification) { notification.getKey().entity.onRemoval(notification); } @Override - public void onHit(Key key, BytesReference value, TierType tierType) { - key.entity.onHit(tierType); + public void onHit(Key key, BytesReference value, CacheStoreType cacheStoreType) { + key.entity.onHit(cacheStoreType); } @Override - public void onCached(Key key, BytesReference value, TierType tierType) { - key.entity.onCached(key, value, tierType); + public void onCached(Key key, BytesReference value, CacheStoreType cacheStoreType) { + key.entity.onCached(key, value, cacheStoreType); } BytesReference getOrCompute( @@ -228,7 +229,7 @@ interface CacheEntity extends Accountable { /** * Called after the value was loaded. */ - void onCached(Key key, BytesReference value, TierType tierType); + void onCached(Key key, BytesReference value, CacheStoreType cacheStoreType); /** * Returns true iff the resource behind this entity is still open ie. @@ -245,12 +246,12 @@ interface CacheEntity extends Accountable { /** * Called each time this entity has a cache hit. */ - void onHit(TierType tierType); + void onHit(CacheStoreType cacheStoreType); /** * Called each time this entity has a cache miss. */ - void onMiss(TierType tierType); + void onMiss(CacheStoreType cacheStoreType); /** * Called when this entity instance is removed diff --git a/server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java index 3cd08df649f72..a10db9bb43f85 100644 --- a/server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java +++ b/server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java @@ -8,9 +8,11 @@ package org.opensearch.common.cache.tier; -import org.opensearch.common.cache.RemovalListener; -import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.cache.RemovalReason; +import org.opensearch.common.cache.tier.enums.CacheStoreType; +import org.opensearch.common.cache.tier.listeners.TieredCacheEventListener; +import org.opensearch.common.cache.tier.listeners.TieredCacheRemovalListener; +import org.opensearch.common.cache.tier.service.TieredCacheSpilloverStrategyService; import org.opensearch.common.metrics.CounterMetric; import org.opensearch.test.OpenSearchTestCase; @@ -40,9 +42,9 @@ public void testComputeAndAbsentWithoutAnyOnHeapCacheEviction() throws Exception TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); spilloverStrategyService.computeIfAbsent(key, tieredCacheLoader); } - assertEquals(numOfItems1, eventListener.enumMap.get(TierType.ON_HEAP).missCount.count()); - assertEquals(0, eventListener.enumMap.get(TierType.ON_HEAP).hitCount.count()); - assertEquals(0, eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count()); + assertEquals(numOfItems1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count()); // Try to hit cache again with some randomization. int numOfItems2 = randomIntBetween(1, onHeapCacheSize / 2 - 1); @@ -60,9 +62,9 @@ public void testComputeAndAbsentWithoutAnyOnHeapCacheEviction() throws Exception cacheMiss++; } } - assertEquals(cacheHit, eventListener.enumMap.get(TierType.ON_HEAP).hitCount.count()); - assertEquals(numOfItems1 + cacheMiss, eventListener.enumMap.get(TierType.ON_HEAP).missCount.count()); - assertEquals(0, eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count()); + assertEquals(cacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); + assertEquals(numOfItems1 + cacheMiss, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count()); } public void testComputeAndAbsentWithEvictionsFromOnHeapCache() throws Exception { @@ -91,15 +93,15 @@ public void testComputeAndAbsentWithEvictionsFromOnHeapCache() throws Exception TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); spilloverStrategyService.computeIfAbsent(key, tieredCacheLoader); } - assertEquals(numOfItems1, eventListener.enumMap.get(TierType.ON_HEAP).missCount.count()); - assertEquals(0, eventListener.enumMap.get(TierType.ON_HEAP).hitCount.count()); - assertTrue(eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count() > 0); + assertEquals(numOfItems1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); + assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); assertEquals( - eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count(), - eventListener.enumMap.get(TierType.DISK).cachedCount.count() + eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count(), + eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count() ); - assertEquals(diskTierKeys.size(), eventListener.enumMap.get(TierType.DISK).cachedCount.count()); + assertEquals(diskTierKeys.size(), eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count()); // Try to hit cache again with some randomization. int numOfItems2 = randomIntBetween(50, 200); @@ -125,10 +127,10 @@ public void testComputeAndAbsentWithEvictionsFromOnHeapCache() throws Exception } } // On heap cache misses would also include diskCacheHits as it means it missed onHeap cache. - assertEquals(numOfItems1 + cacheMiss + diskCacheHit, eventListener.enumMap.get(TierType.ON_HEAP).missCount.count()); - assertEquals(onHeapCacheHit, eventListener.enumMap.get(TierType.ON_HEAP).hitCount.count()); - assertEquals(cacheMiss + numOfItems1, eventListener.enumMap.get(TierType.DISK).missCount.count()); - assertEquals(diskCacheHit, eventListener.enumMap.get(TierType.DISK).hitCount.count()); + assertEquals(numOfItems1 + cacheMiss + diskCacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); + assertEquals(onHeapCacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); + assertEquals(cacheMiss + numOfItems1, eventListener.enumMap.get(CacheStoreType.DISK).missCount.count()); + assertEquals(diskCacheHit, eventListener.enumMap.get(CacheStoreType.DISK).hitCount.count()); } public void testComputeAndAbsentWithEvictionsFromBothTier() throws Exception { @@ -148,8 +150,8 @@ public void testComputeAndAbsentWithEvictionsFromBothTier() throws Exception { TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); } - assertTrue(eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count() > 0); - assertTrue(eventListener.enumMap.get(TierType.DISK).evictionsMetric.count() > 0); + assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); + assertTrue(eventListener.enumMap.get(CacheStoreType.DISK).evictionsMetric.count() > 0); } public void testGetAndCount() throws Exception { @@ -206,10 +208,10 @@ public void testWithDiskTierNull() throws Exception { TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); } - assertTrue(eventListener.enumMap.get(TierType.ON_HEAP).evictionsMetric.count() > 0); - assertEquals(0, eventListener.enumMap.get(TierType.DISK).cachedCount.count()); - assertEquals(0, eventListener.enumMap.get(TierType.DISK).evictionsMetric.count()); - assertEquals(0, eventListener.enumMap.get(TierType.DISK).missCount.count()); + assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.DISK).evictionsMetric.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.DISK).missCount.count()); } private TieredCacheLoader getTieredCacheLoader() { @@ -242,11 +244,11 @@ private TieredCacheSpilloverStrategyService intializeTieredCache .build(); } - class MockOnHeapCacheTier implements OnHeapCachingTier, RemovalListener { + class MockOnHeapCacheTier implements OnHeapCachingTier, TieredCacheRemovalListener { Map onHeapCacheTier; int maxSize; - private RemovalListener removalListener; + private TieredCacheRemovalListener removalListener; MockOnHeapCacheTier(int size) { maxSize = size; @@ -266,7 +268,7 @@ public void put(K key, V value) { @Override public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { if (this.onHeapCacheTier.size() > maxSize) { // If it exceeds, just notify for evict. - onRemoval(new RemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, TierType.ON_HEAP)); + onRemoval(new TieredCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP)); return loader.load(key); } return this.onHeapCacheTier.computeIfAbsent(key, k -> { @@ -286,7 +288,7 @@ public void invalidate(K key) { @Override public V compute(K key, TieredCacheLoader loader) throws Exception { if (this.onHeapCacheTier.size() >= maxSize) { // If it exceeds, just notify for evict. - onRemoval(new RemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, TierType.ON_HEAP)); + onRemoval(new TieredCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP)); return loader.load(key); } return this.onHeapCacheTier.compute(key, ((k, v) -> { @@ -299,7 +301,7 @@ public V compute(K key, TieredCacheLoader loader) throws Exception { } @Override - public void setRemovalListener(RemovalListener removalListener) { + public void setRemovalListener(TieredCacheRemovalListener removalListener) { this.removalListener = removalListener; } @@ -319,46 +321,46 @@ public int count() { } @Override - public TierType getTierType() { - return TierType.ON_HEAP; + public CacheStoreType getTierType() { + return CacheStoreType.ON_HEAP; } @Override - public void onRemoval(RemovalNotification notification) { + public void onRemoval(TieredCacheRemovalNotification notification) { removalListener.onRemoval(notification); } } class MockTieredCacheEventListener implements TieredCacheEventListener { - EnumMap enumMap = new EnumMap<>(TierType.class); + EnumMap enumMap = new EnumMap<>(CacheStoreType.class); MockTieredCacheEventListener() { - for (TierType tierType : TierType.values()) { - enumMap.put(tierType, new TestStatsHolder()); + for (CacheStoreType cacheStoreType : CacheStoreType.values()) { + enumMap.put(cacheStoreType, new TestStatsHolder()); } } @Override - public void onMiss(K key, TierType tierType) { - enumMap.get(tierType).missCount.inc(); + public void onMiss(K key, CacheStoreType cacheStoreType) { + enumMap.get(cacheStoreType).missCount.inc(); } @Override - public void onRemoval(RemovalNotification notification) { + public void onRemoval(TieredCacheRemovalNotification notification) { if (notification.getRemovalReason().equals(RemovalReason.EVICTED)) { enumMap.get(notification.getTierType()).evictionsMetric.inc(); } } @Override - public void onHit(K key, V value, TierType tierType) { - enumMap.get(tierType).hitCount.inc(); + public void onHit(K key, V value, CacheStoreType cacheStoreType) { + enumMap.get(cacheStoreType).hitCount.inc(); } @Override - public void onCached(K key, V value, TierType tierType) { - enumMap.get(tierType).cachedCount.inc(); + public void onCached(K key, V value, CacheStoreType cacheStoreType) { + enumMap.get(cacheStoreType).cachedCount.inc(); } class TestStatsHolder { @@ -370,9 +372,9 @@ class TestStatsHolder { } } - class MockDiskCachingTier implements DiskCachingTier, RemovalListener { + class MockDiskCachingTier implements DiskCachingTier, TieredCacheRemovalListener { Map diskTier; - private RemovalListener removalListener; + private TieredCacheRemovalListener removalListener; int maxSize; MockDiskCachingTier(int size) { @@ -388,7 +390,7 @@ public V get(K key) { @Override public void put(K key, V value) { if (this.diskTier.size() >= maxSize) { // For simplification - onRemoval(new RemovalNotification<>(key, value, RemovalReason.EVICTED, TierType.DISK)); + onRemoval(new TieredCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.DISK)); return; } this.diskTier.put(key, value); @@ -413,7 +415,7 @@ public void invalidate(K key) { @Override public V compute(K key, TieredCacheLoader loader) throws Exception { if (this.diskTier.size() >= maxSize) { // If it exceeds, just notify for evict. - onRemoval(new RemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, TierType.DISK)); + onRemoval(new TieredCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.DISK)); return loader.load(key); } return this.diskTier.compute(key, (k, v) -> { @@ -426,7 +428,7 @@ public V compute(K key, TieredCacheLoader loader) throws Exception { } @Override - public void setRemovalListener(RemovalListener removalListener) { + public void setRemovalListener(TieredCacheRemovalListener removalListener) { this.removalListener = removalListener; } @@ -446,12 +448,12 @@ public int count() { } @Override - public TierType getTierType() { - return TierType.DISK; + public CacheStoreType getTierType() { + return CacheStoreType.DISK; } @Override - public void onRemoval(RemovalNotification notification) { + public void onRemoval(TieredCacheRemovalNotification notification) { this.removalListener.onRemoval(notification); } } diff --git a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java index 38ff7ba8cef21..9de45835d3c2d 100644 --- a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java +++ b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java @@ -37,7 +37,7 @@ import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.routing.allocation.DiskThresholdSettings; import org.opensearch.common.cache.RemovalNotification; -import org.opensearch.common.cache.tier.TierType; +import org.opensearch.common.cache.tier.enums.CacheStoreType; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.OpenSearchExecutors; import org.opensearch.core.common.bytes.BytesArray; @@ -322,7 +322,7 @@ public long ramBytesUsed() { } @Override - public void onCached(Key key, BytesReference value, TierType tierType) {} + public void onCached(Key key, BytesReference value, CacheStoreType tierType) {} @Override public boolean isOpen() { @@ -335,10 +335,10 @@ public Object getCacheIdentity() { } @Override - public void onHit(TierType tierType) {} + public void onHit(CacheStoreType tierType) {} @Override - public void onMiss(TierType tierType) {} + public void onMiss(CacheStoreType tierType) {} @Override public void onRemoval(RemovalNotification notification) {} From c29d346cef0551c064e2530466708cb5f78104d5 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Wed, 22 Nov 2023 05:59:24 -0800 Subject: [PATCH 06/19] Addressing additional minor comments Signed-off-by: Sagar Upadhyaya --- .../org/opensearch/common/cache/tier/CachingTier.java | 2 ++ .../tier/service/TieredCacheSpilloverStrategyService.java | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java b/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java index 11cbc562d5941..9902244363f89 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java @@ -16,6 +16,8 @@ * onHeap, disk etc. * @param Type of key * @param Type of value + * + * @opensearch.internal */ public interface CachingTier { diff --git a/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java b/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java index 269ee24e364c0..7b06ae5d3cb16 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java @@ -65,7 +65,7 @@ private TieredCacheSpilloverStrategyService(Builder builder) { */ @Override public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { - CacheValue cacheValue = getValueFromTierCache(true).apply(key); + CacheValue cacheValue = getValueFromTieredCache(true).apply(key); if (cacheValue == null) { // Add the value to the onHeap cache. Any items if evicted will be moved to lower tier. V value = onHeapCachingTier.compute(key, loader); @@ -77,7 +77,7 @@ public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception @Override public V get(K key) { - CacheValue cacheValue = getValueFromTierCache(true).apply(key); + CacheValue cacheValue = getValueFromTieredCache(true).apply(key); if (cacheValue == null) { return null; } @@ -91,7 +91,7 @@ public V get(K key) { @Override public void invalidate(K key) { // We don't need to track hits/misses in this case. - CacheValue cacheValue = getValueFromTierCache(false).apply(key); + CacheValue cacheValue = getValueFromTieredCache(false).apply(key); if (cacheValue != null) { switch (cacheValue.source) { case ON_HEAP: @@ -167,7 +167,7 @@ private void setRemovalListeners() { } } - private Function> getValueFromTierCache(boolean trackStats) { + private Function> getValueFromTieredCache(boolean trackStats) { return key -> { for (CachingTier cachingTier : cachingTierList) { V value = cachingTier.get(key); From 2e024a2fee5a722d0d42735f3fbd43bb6d5fc807 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Wed, 22 Nov 2023 06:06:59 -0800 Subject: [PATCH 07/19] Moving non null check to builder for OS onHeapCache Signed-off-by: Sagar Upadhyaya --- .../org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java b/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java index 91720dd3ecabc..0ff785936770d 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java @@ -31,7 +31,6 @@ public class OpenSearchOnHeapCache implements OnHeapCachingTier, Rem private TieredCacheRemovalListener removalListener; private OpenSearchOnHeapCache(Builder builder) { - Objects.requireNonNull(builder.weigher); CacheBuilder cacheBuilder = CacheBuilder.builder() .setMaximumWeight(builder.maxWeightInBytes) .weigher(builder.weigher) @@ -123,6 +122,7 @@ public Builder setMaximumWeight(long sizeInBytes) { } public Builder setWeigher(ToLongBiFunction weigher) { + Objects.requireNonNull(weigher); this.weigher = weigher; return this; } From 51d948fe5865a12fb43e79403d59e01b2d4e2851 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 23 Nov 2023 01:32:15 -0800 Subject: [PATCH 08/19] Adding package-info for new packages Signed-off-by: Sagar Upadhyaya --- .../common/cache/tier/enums/package-info.java | 10 ++++++++++ .../common/cache/tier/listeners/package-info.java | 10 ++++++++++ .../common/cache/tier/service/package-info.java | 10 ++++++++++ 3 files changed, 30 insertions(+) create mode 100644 server/src/main/java/org/opensearch/common/cache/tier/enums/package-info.java create mode 100644 server/src/main/java/org/opensearch/common/cache/tier/listeners/package-info.java create mode 100644 server/src/main/java/org/opensearch/common/cache/tier/service/package-info.java diff --git a/server/src/main/java/org/opensearch/common/cache/tier/enums/package-info.java b/server/src/main/java/org/opensearch/common/cache/tier/enums/package-info.java new file mode 100644 index 0000000000000..f1403a41cd40e --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/tier/enums/package-info.java @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** Package related to tiered cache enums */ +package org.opensearch.common.cache.tier.enums; diff --git a/server/src/main/java/org/opensearch/common/cache/tier/listeners/package-info.java b/server/src/main/java/org/opensearch/common/cache/tier/listeners/package-info.java new file mode 100644 index 0000000000000..d25b3be574af0 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/tier/listeners/package-info.java @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** Package related to tiered cache listeners */ +package org.opensearch.common.cache.tier.listeners; diff --git a/server/src/main/java/org/opensearch/common/cache/tier/service/package-info.java b/server/src/main/java/org/opensearch/common/cache/tier/service/package-info.java new file mode 100644 index 0000000000000..3030663c82045 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/tier/service/package-info.java @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** Package related to tiered cache services */ +package org.opensearch.common.cache.tier.service; From 64edcfb51151a4b0a312a185220a1a790416aef2 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 7 Dec 2023 06:07:10 -0800 Subject: [PATCH 09/19] Removing service and adding different cache interfaces along with event listener support Signed-off-by: Sagar Upadhyaya --- CHANGELOG.md | 3 +- .../common/cache/LoadAwareCacheLoader.java | 18 + .../opensearch/common/cache/store/Cache.java | 36 + .../cache/store/OpenSearchOnHeapCache.java | 135 +++ .../common/cache/store/StoreAwareCache.java | 20 + .../StoreAwareCacheRemovalNotification.java | 26 + .../cache/store/StoreAwareCacheValue.java | 33 + .../builders/StoreAwareCacheBuilder.java | 73 ++ .../builders}/package-info.java | 6 +- .../{tier => store}/enums/CacheStoreType.java | 2 +- .../{tier => store}/enums/package-info.java | 2 +- .../cache/store/listeners/EventType.java | 20 + .../StoreAwareCacheEventListener.java} | 10 +- ...eAwareCacheEventListenerConfiguration.java | 52 ++ ...toreAwareCacheEventListenerDispatcher.java | 25 + ...areCacheListenerDispatcherDefaultImpl.java | 62 ++ .../listeners/dispatchers/package-info.java | 12 + .../listeners/package-info.java | 2 +- .../common/cache/store/package-info.java | 10 + .../common/cache/tier/CachingTier.java | 48 -- .../common/cache/tier/DiskCachingTier.java | 18 - .../common/cache/tier/OnHeapCachingTier.java | 16 - .../cache/tier/OpenSearchOnHeapCache.java | 139 ---- .../common/cache/tier/TieredCache.java | 23 + .../common/cache/tier/TieredCacheLoader.java | 20 - .../tier/TieredCacheRemovalNotification.java | 38 - .../cache/tier/TieredSpilloverCache.java | 291 +++++++ .../listeners/TieredCacheRemovalListener.java | 21 - .../tier/service/TieredCacheService.java | 38 - .../TieredCacheSpilloverStrategyService.java | 234 ------ .../cache/request/ShardRequestCache.java | 2 +- .../AbstractIndexShardCacheEntity.java | 14 +- .../indices/IndicesRequestCache.java | 78 +- ...redCacheSpilloverStrategyServiceTests.java | 460 ----------- .../cache/tier/TieredSpilloverCacheTests.java | 769 ++++++++++++++++++ .../indices/IndicesServiceCloseTests.java | 2 +- 36 files changed, 1676 insertions(+), 1082 deletions(-) create mode 100644 server/src/main/java/org/opensearch/common/cache/LoadAwareCacheLoader.java create mode 100644 server/src/main/java/org/opensearch/common/cache/store/Cache.java create mode 100644 server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java create mode 100644 server/src/main/java/org/opensearch/common/cache/store/StoreAwareCache.java create mode 100644 server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java create mode 100644 server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java create mode 100644 server/src/main/java/org/opensearch/common/cache/store/builders/StoreAwareCacheBuilder.java rename server/src/main/java/org/opensearch/common/cache/{tier/service => store/builders}/package-info.java (67%) rename server/src/main/java/org/opensearch/common/cache/{tier => store}/enums/CacheStoreType.java (86%) rename server/src/main/java/org/opensearch/common/cache/{tier => store}/enums/package-info.java (83%) create mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java rename server/src/main/java/org/opensearch/common/cache/{tier/listeners/TieredCacheEventListener.java => store/listeners/StoreAwareCacheEventListener.java} (61%) create mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java create mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheEventListenerDispatcher.java create mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheListenerDispatcherDefaultImpl.java create mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/package-info.java rename server/src/main/java/org/opensearch/common/cache/{tier => store}/listeners/package-info.java (82%) create mode 100644 server/src/main/java/org/opensearch/common/cache/store/package-info.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/DiskCachingTier.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/OnHeapCachingTier.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java create mode 100644 server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/TieredCacheLoader.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/TieredCacheRemovalNotification.java create mode 100644 server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheRemovalListener.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheService.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java delete mode 100644 server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java create mode 100644 server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6754ed8162360..539984c956ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,7 +105,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Update the indexRandom function to create more segments for concurrent search tests ([10247](https://github.com/opensearch-project/OpenSearch/pull/10247)) - [Remote cluster state] Make index and global metadata upload timeout dynamic cluster settings ([#10814](https://github.com/opensearch-project/OpenSearch/pull/10814)) - Add cluster state stats ([#10670](https://github.com/opensearch-project/OpenSearch/pull/10670)) -- [Tiered caching] Defining interfaces, service and extending IndicesRequestCache with basic Tier support ([#10753] (https://github.com/opensearch-project/OpenSearch/pull/10753)) +- [Tiered caching] Defining interfaces, listeners and extending IndicesRequestCache with Tiered cache support ([#10753] + (https://github.com/opensearch-project/OpenSearch/pull/10753)) - Adding slf4j license header to LoggerMessageFormat.java ([#11069](https://github.com/opensearch-project/OpenSearch/pull/11069)) - [Streaming Indexing] Introduce new experimental server HTTP transport based on Netty 4 and Project Reactor (Reactor Netty) ([#9672](https://github.com/opensearch-project/OpenSearch/pull/9672)) - Add template snippets support for field and target_field in KV ingest processor ([#10040](https://github.com/opensearch-project/OpenSearch/pull/10040)) diff --git a/server/src/main/java/org/opensearch/common/cache/LoadAwareCacheLoader.java b/server/src/main/java/org/opensearch/common/cache/LoadAwareCacheLoader.java new file mode 100644 index 0000000000000..1cc47f93bdf7d --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/LoadAwareCacheLoader.java @@ -0,0 +1,18 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache; + +/** + * Extends a cache loader with awareness of whether the data is loaded or not. + * @param Type of key. + * @param Type of value. + */ +public interface LoadAwareCacheLoader extends CacheLoader { + boolean isLoaded(); +} diff --git a/server/src/main/java/org/opensearch/common/cache/store/Cache.java b/server/src/main/java/org/opensearch/common/cache/store/Cache.java new file mode 100644 index 0000000000000..4b6167ddb2c7b --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/Cache.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store; + +import org.opensearch.common.cache.LoadAwareCacheLoader; + +/** + * Represents a cache interface. + * @param Type of key. + * @param Type of value. + */ +public interface Cache { + V get(K key); + + void put(K key, V value); + + V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception; + + void invalidate(K key); + + V compute(K key, LoadAwareCacheLoader loader) throws Exception; + + void invalidateAll(); + + Iterable keys(); + + long count(); + + void refresh(); +} diff --git a/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java b/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java new file mode 100644 index 0000000000000..067876119b6a9 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java @@ -0,0 +1,135 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store; + +import org.opensearch.common.cache.Cache; +import org.opensearch.common.cache.CacheBuilder; +import org.opensearch.common.cache.LoadAwareCacheLoader; +import org.opensearch.common.cache.RemovalListener; +import org.opensearch.common.cache.RemovalNotification; +import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; +import org.opensearch.common.cache.store.enums.CacheStoreType; +import org.opensearch.common.cache.store.listeners.EventType; +import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheEventListenerDispatcher; +import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheListenerDispatcherDefaultImpl; + +/** + * This variant of on-heap cache uses OpenSearch custom cache implementation. + * @param Type of key. + * @param Type of value. + */ +public class OpenSearchOnHeapCache implements StoreAwareCache, RemovalListener { + + private final Cache cache; + + private final StoreAwareCacheEventListenerDispatcher eventDispatcher; + + public OpenSearchOnHeapCache(Builder builder) { + CacheBuilder cacheBuilder = CacheBuilder.builder() + .setMaximumWeight(builder.getMaxWeightInBytes()) + .weigher(builder.getWeigher()) + .removalListener(this); + if (builder.getExpireAfterAcess() != null) { + cacheBuilder.setExpireAfterAccess(builder.getExpireAfterAcess()); + } + cache = cacheBuilder.build(); + this.eventDispatcher = new StoreAwareCacheListenerDispatcherDefaultImpl<>(builder.getListenerConfiguration()); + } + + @Override + public V get(K key) { + V value = cache.get(key); + if (value != null) { + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_HIT); + } else { + eventDispatcher.dispatch(key, null, CacheStoreType.ON_HEAP, EventType.ON_MISS); + } + return value; + } + + @Override + public void put(K key, V value) { + cache.put(key, value); + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + } + + @Override + public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { + V value = cache.computeIfAbsent(key, key1 -> loader.load(key)); + if (!loader.isLoaded()) { + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_HIT); + } else { + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_MISS); + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + } + return value; + } + + @Override + public void invalidate(K key) { + cache.invalidate(key); + } + + @Override + public V compute(K key, LoadAwareCacheLoader loader) throws Exception { + V value = cache.compute(key, key1 -> loader.load(key)); + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + return value; + } + + @Override + public void invalidateAll() { + cache.invalidateAll(); + } + + @Override + public Iterable keys() { + return cache.keys(); + } + + @Override + public long count() { + return cache.count(); + } + + @Override + public void refresh() { + cache.refresh(); + } + + @Override + public CacheStoreType getTierType() { + return CacheStoreType.ON_HEAP; + } + + @Override + public void onRemoval(RemovalNotification notification) { + eventDispatcher.dispatchRemovalEvent( + new StoreAwareCacheRemovalNotification<>( + notification.getKey(), + notification.getValue(), + notification.getRemovalReason(), + CacheStoreType.ON_HEAP + ) + ); + } + + /** + * Builder object + * @param Type of key + * @param Type of value + */ + public static class Builder extends StoreAwareCacheBuilder { + + @Override + public StoreAwareCache build() { + return new OpenSearchOnHeapCache(this); + } + } +} diff --git a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCache.java b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCache.java new file mode 100644 index 0000000000000..f06802687e6ad --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCache.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store; + +import org.opensearch.common.cache.store.enums.CacheStoreType; + +/** + * Represents a cache with a specific type of store like onHeap, disk etc. + * @param Type of key. + * @param Type of value. + */ +public interface StoreAwareCache extends Cache { + CacheStoreType getTierType(); +} diff --git a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java new file mode 100644 index 0000000000000..91c3b6c2c5e33 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store; + +import org.opensearch.common.cache.RemovalNotification; +import org.opensearch.common.cache.RemovalReason; +import org.opensearch.common.cache.store.enums.CacheStoreType; + +public class StoreAwareCacheRemovalNotification extends RemovalNotification { + private final CacheStoreType cacheStoreType; + + public StoreAwareCacheRemovalNotification(K key, V value, RemovalReason removalReason, CacheStoreType cacheStoreType) { + super(key, value, removalReason); + this.cacheStoreType = cacheStoreType; + } + + public CacheStoreType getCacheStoreType() { + return cacheStoreType; + } +} diff --git a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java new file mode 100644 index 0000000000000..ac036f2f197d0 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store; + +import org.opensearch.common.cache.store.enums.CacheStoreType; + +/** + * Represents a store aware cache value. + * @param Type of value. + */ +public class StoreAwareCacheValue { + private final V value; + private final CacheStoreType source; + + public StoreAwareCacheValue(V value, CacheStoreType source) { + this.value = value; + this.source = source; + } + + public V getValue() { + return value; + } + + public CacheStoreType getSource() { + return source; + } +} diff --git a/server/src/main/java/org/opensearch/common/cache/store/builders/StoreAwareCacheBuilder.java b/server/src/main/java/org/opensearch/common/cache/store/builders/StoreAwareCacheBuilder.java new file mode 100644 index 0000000000000..2da9fde277c4e --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/builders/StoreAwareCacheBuilder.java @@ -0,0 +1,73 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store.builders; + +import org.opensearch.common.cache.store.StoreAwareCache; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; +import org.opensearch.common.unit.TimeValue; + +import java.util.function.ToLongBiFunction; + +/** + * Builder for store aware cache. + * @param Type of key. + * @param Type of value. + */ +public abstract class StoreAwareCacheBuilder { + + private long maxWeightInBytes; + + private ToLongBiFunction weigher; + + private TimeValue expireAfterAcess; + + private StoreAwareCacheEventListenerConfiguration listenerConfiguration; + + public StoreAwareCacheBuilder() {} + + public StoreAwareCacheBuilder setMaximumWeightInBytes(long sizeInBytes) { + this.maxWeightInBytes = sizeInBytes; + return this; + } + + public StoreAwareCacheBuilder setWeigher(ToLongBiFunction weigher) { + this.weigher = weigher; + return this; + } + + public StoreAwareCacheBuilder setExpireAfterAccess(TimeValue expireAfterAcess) { + this.expireAfterAcess = expireAfterAcess; + return this; + } + + public StoreAwareCacheBuilder setEventListenerConfiguration( + StoreAwareCacheEventListenerConfiguration listenerConfiguration + ) { + this.listenerConfiguration = listenerConfiguration; + return this; + } + + public long getMaxWeightInBytes() { + return maxWeightInBytes; + } + + public TimeValue getExpireAfterAcess() { + return expireAfterAcess; + } + + public ToLongBiFunction getWeigher() { + return weigher; + } + + public StoreAwareCacheEventListenerConfiguration getListenerConfiguration() { + return listenerConfiguration; + } + + public abstract StoreAwareCache build(); +} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/service/package-info.java b/server/src/main/java/org/opensearch/common/cache/store/builders/package-info.java similarity index 67% rename from server/src/main/java/org/opensearch/common/cache/tier/service/package-info.java rename to server/src/main/java/org/opensearch/common/cache/store/builders/package-info.java index 3030663c82045..ac4590ae3bff7 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/service/package-info.java +++ b/server/src/main/java/org/opensearch/common/cache/store/builders/package-info.java @@ -6,5 +6,7 @@ * compatible open source license. */ -/** Package related to tiered cache services */ -package org.opensearch.common.cache.tier.service; +/** + * Base package for builders. + */ +package org.opensearch.common.cache.store.builders; diff --git a/server/src/main/java/org/opensearch/common/cache/tier/enums/CacheStoreType.java b/server/src/main/java/org/opensearch/common/cache/store/enums/CacheStoreType.java similarity index 86% rename from server/src/main/java/org/opensearch/common/cache/tier/enums/CacheStoreType.java rename to server/src/main/java/org/opensearch/common/cache/store/enums/CacheStoreType.java index 80d67f0bb5bcf..9d7d2b57af743 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/enums/CacheStoreType.java +++ b/server/src/main/java/org/opensearch/common/cache/store/enums/CacheStoreType.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.common.cache.tier.enums; +package org.opensearch.common.cache.store.enums; /** * Cache store types in tiered cache. diff --git a/server/src/main/java/org/opensearch/common/cache/tier/enums/package-info.java b/server/src/main/java/org/opensearch/common/cache/store/enums/package-info.java similarity index 83% rename from server/src/main/java/org/opensearch/common/cache/tier/enums/package-info.java rename to server/src/main/java/org/opensearch/common/cache/store/enums/package-info.java index f1403a41cd40e..7a4e0fa7201fd 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/enums/package-info.java +++ b/server/src/main/java/org/opensearch/common/cache/store/enums/package-info.java @@ -7,4 +7,4 @@ */ /** Package related to tiered cache enums */ -package org.opensearch.common.cache.tier.enums; +package org.opensearch.common.cache.store.enums; diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java new file mode 100644 index 0000000000000..ceecba787ef55 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java @@ -0,0 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store.listeners; + +/** + * Describes various event types which is used to notify the called via listener. + */ +public enum EventType { + + ON_MISS, + ON_HIT, + ON_CACHED, + ON_REMOVAL; +} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheEventListener.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListener.java similarity index 61% rename from server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheEventListener.java rename to server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListener.java index 03489772f17e2..76d669b50c696 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheEventListener.java +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListener.java @@ -6,21 +6,21 @@ * compatible open source license. */ -package org.opensearch.common.cache.tier.listeners; +package org.opensearch.common.cache.store.listeners; -import org.opensearch.common.cache.tier.enums.CacheStoreType; -import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; +import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; +import org.opensearch.common.cache.store.enums.CacheStoreType; /** * This can be used to listen to tiered caching events * @param Type of key * @param Type of value */ -public interface TieredCacheEventListener { +public interface StoreAwareCacheEventListener { void onMiss(K key, CacheStoreType cacheStoreType); - void onRemoval(TieredCacheRemovalNotification notification); + void onRemoval(StoreAwareCacheRemovalNotification notification); void onHit(K key, V value, CacheStoreType cacheStoreType); diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java new file mode 100644 index 0000000000000..be49c82238ae8 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store.listeners; + +import java.util.EnumSet; +import java.util.Objects; + +public class StoreAwareCacheEventListenerConfiguration { + + private final EnumSet eventTypes; + private final StoreAwareCacheEventListener eventListener; + + public StoreAwareCacheEventListenerConfiguration(Builder builder) { + this.eventListener = Objects.requireNonNull(builder.eventListener); + this.eventTypes = Objects.requireNonNull(builder.eventTypes); + } + + public EnumSet getEventTypes() { + return eventTypes; + } + + public StoreAwareCacheEventListener getEventListener() { + return eventListener; + } + + public static class Builder { + private EnumSet eventTypes; + private StoreAwareCacheEventListener eventListener; + + public Builder() {} + + public Builder setEventTypes(EnumSet eventTypes) { + this.eventTypes = eventTypes; + return this; + } + + public Builder setEventListener(StoreAwareCacheEventListener eventListener) { + this.eventListener = eventListener; + return this; + } + + public StoreAwareCacheEventListenerConfiguration build() { + return new StoreAwareCacheEventListenerConfiguration<>(this); + } + } +} diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheEventListenerDispatcher.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheEventListenerDispatcher.java new file mode 100644 index 0000000000000..e67edbd602a56 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheEventListenerDispatcher.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store.listeners.dispatchers; + +import org.opensearch.common.Nullable; +import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; +import org.opensearch.common.cache.store.enums.CacheStoreType; +import org.opensearch.common.cache.store.listeners.EventType; + +/** + * Encapsulates all logic to dispatch events for specific cache store type. + * @param Type of key. + * @param Type of value. + */ +public interface StoreAwareCacheEventListenerDispatcher { + void dispatch(K key, @Nullable V value, CacheStoreType cacheStoreType, EventType eventType); + + void dispatchRemovalEvent(StoreAwareCacheRemovalNotification notification); +} diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheListenerDispatcherDefaultImpl.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheListenerDispatcherDefaultImpl.java new file mode 100644 index 0000000000000..ae4baf8565528 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheListenerDispatcherDefaultImpl.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.store.listeners.dispatchers; + +import org.opensearch.common.Nullable; +import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; +import org.opensearch.common.cache.store.enums.CacheStoreType; +import org.opensearch.common.cache.store.listeners.EventType; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; + +/** + * Default implementation of event listener dispatcher. + * @param Type of key. + * @param Type of value. + */ +public class StoreAwareCacheListenerDispatcherDefaultImpl implements StoreAwareCacheEventListenerDispatcher { + + private final StoreAwareCacheEventListenerConfiguration listenerConfiguration; + + public StoreAwareCacheListenerDispatcherDefaultImpl(StoreAwareCacheEventListenerConfiguration listenerConfiguration) { + this.listenerConfiguration = listenerConfiguration; + } + + @Override + public void dispatch(K key, @Nullable V value, CacheStoreType cacheStoreType, EventType eventType) { + switch (eventType) { + case ON_CACHED: + if (this.listenerConfiguration.getEventTypes().contains(EventType.ON_CACHED)) { + this.listenerConfiguration.getEventListener().onCached(key, value, cacheStoreType); + } + break; + case ON_HIT: + if (this.listenerConfiguration.getEventTypes().contains(EventType.ON_HIT)) { + this.listenerConfiguration.getEventListener().onHit(key, value, cacheStoreType); + } + break; + case ON_MISS: + if (this.listenerConfiguration.getEventTypes().contains(EventType.ON_MISS)) { + this.listenerConfiguration.getEventListener().onMiss(key, cacheStoreType); + } + break; + case ON_REMOVAL: + // Handled separately + break; + default: + throw new IllegalArgumentException("Unsupported event type: " + eventType); + } + } + + @Override + public void dispatchRemovalEvent(StoreAwareCacheRemovalNotification notification) { + if (this.listenerConfiguration.getEventTypes().contains(EventType.ON_REMOVAL)) { + this.listenerConfiguration.getEventListener().onRemoval(notification); + } + } +} diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/package-info.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/package-info.java new file mode 100644 index 0000000000000..a155bbba82bfc --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * Base package for listener dispatchers. + */ +package org.opensearch.common.cache.store.listeners.dispatchers; diff --git a/server/src/main/java/org/opensearch/common/cache/tier/listeners/package-info.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/package-info.java similarity index 82% rename from server/src/main/java/org/opensearch/common/cache/tier/listeners/package-info.java rename to server/src/main/java/org/opensearch/common/cache/store/listeners/package-info.java index d25b3be574af0..c3222ca3ffb62 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/listeners/package-info.java +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/package-info.java @@ -7,4 +7,4 @@ */ /** Package related to tiered cache listeners */ -package org.opensearch.common.cache.tier.listeners; +package org.opensearch.common.cache.store.listeners; diff --git a/server/src/main/java/org/opensearch/common/cache/store/package-info.java b/server/src/main/java/org/opensearch/common/cache/store/package-info.java new file mode 100644 index 0000000000000..edc1ecd7d5e7a --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/store/package-info.java @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** Base package for store aware caches. */ +package org.opensearch.common.cache.store; diff --git a/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java b/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java deleted file mode 100644 index 9902244363f89..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/CachingTier.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier; - -import org.opensearch.common.cache.tier.enums.CacheStoreType; -import org.opensearch.common.cache.tier.listeners.TieredCacheRemovalListener; - -/** - * Caching tier interface. Can be implemented/extended by concrete classes to provide different flavors of cache like - * onHeap, disk etc. - * @param Type of key - * @param Type of value - * - * @opensearch.internal - */ -public interface CachingTier { - - V get(K key); - - void put(K key, V value); - - V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception; - - void invalidate(K key); - - V compute(K key, TieredCacheLoader loader) throws Exception; - - void setRemovalListener(TieredCacheRemovalListener removalListener); - - void invalidateAll(); - - Iterable keys(); - - int count(); - - CacheStoreType getTierType(); - - /** - * Force any outstanding size-based and time-based evictions to occur - */ - default void refresh() {} -} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/DiskCachingTier.java b/server/src/main/java/org/opensearch/common/cache/tier/DiskCachingTier.java deleted file mode 100644 index 4db71b6378a02..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/DiskCachingTier.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier; - -/** - * This is specific to disk caching tier and can be used to add methods which are specific to disk tier. - * @param Type of key - * @param Type of value - */ -public interface DiskCachingTier extends CachingTier { - -} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/OnHeapCachingTier.java b/server/src/main/java/org/opensearch/common/cache/tier/OnHeapCachingTier.java deleted file mode 100644 index 127fa6ee8e6b3..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/OnHeapCachingTier.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier; - -/** - * This is specific to onHeap caching tier and can be used to add methods which are specific to this tier. - * @param Type of key - * @param Type of value - */ -public interface OnHeapCachingTier extends CachingTier {} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java b/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java deleted file mode 100644 index 0ff785936770d..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/OpenSearchOnHeapCache.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier; - -import org.opensearch.common.cache.Cache; -import org.opensearch.common.cache.CacheBuilder; -import org.opensearch.common.cache.RemovalListener; -import org.opensearch.common.cache.RemovalNotification; -import org.opensearch.common.cache.tier.enums.CacheStoreType; -import org.opensearch.common.cache.tier.listeners.TieredCacheRemovalListener; -import org.opensearch.common.unit.TimeValue; - -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.function.ToLongBiFunction; - -/** - * This variant of on-heap cache uses OpenSearch custom cache implementation. - * @param Type of key - * @param Type of value - */ -public class OpenSearchOnHeapCache implements OnHeapCachingTier, RemovalListener { - - private final Cache cache; - private TieredCacheRemovalListener removalListener; - - private OpenSearchOnHeapCache(Builder builder) { - CacheBuilder cacheBuilder = CacheBuilder.builder() - .setMaximumWeight(builder.maxWeightInBytes) - .weigher(builder.weigher) - .removalListener(this); - if (builder.expireAfterAcess != null) { - cacheBuilder.setExpireAfterAccess(builder.expireAfterAcess); - } - cache = cacheBuilder.build(); - } - - @Override - public void setRemovalListener(TieredCacheRemovalListener removalListener) { - this.removalListener = removalListener; - } - - @Override - public void invalidateAll() { - cache.invalidateAll(); - } - - @Override - public Iterable keys() { - return this.cache.keys(); - } - - @Override - public int count() { - return cache.count(); - } - - @Override - public CacheStoreType getTierType() { - return CacheStoreType.ON_HEAP; - } - - @Override - public V get(K key) { - return cache.get(key); - } - - @Override - public void put(K key, V value) { - cache.put(key, value); - } - - @Override - public V computeIfAbsent(K key, TieredCacheLoader loader) throws ExecutionException { - return cache.computeIfAbsent(key, key1 -> loader.load(key)); - } - - @Override - public void invalidate(K key) { - cache.invalidate(key); - } - - @Override - public V compute(K key, TieredCacheLoader loader) throws Exception { - return cache.compute(key, key1 -> loader.load(key)); - } - - @Override - public void refresh() { - cache.refresh(); - } - - @Override - public void onRemoval(RemovalNotification notification) { - removalListener.onRemoval(new TieredCacheRemovalNotification<>(notification.getKey(), notification.getValue() - , notification.getRemovalReason(), CacheStoreType.ON_HEAP)); - } - - /** - * Builder object - * @param Type of key - * @param Type of value - */ - public static class Builder { - private long maxWeightInBytes; - - private ToLongBiFunction weigher; - - private TimeValue expireAfterAcess; - - public Builder() {} - - public Builder setMaximumWeight(long sizeInBytes) { - this.maxWeightInBytes = sizeInBytes; - return this; - } - - public Builder setWeigher(ToLongBiFunction weigher) { - Objects.requireNonNull(weigher); - this.weigher = weigher; - return this; - } - - public Builder setExpireAfterAccess(TimeValue expireAfterAcess) { - this.expireAfterAcess = expireAfterAcess; - return this; - } - - public OpenSearchOnHeapCache build() { - return new OpenSearchOnHeapCache(this); - } - } -} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java new file mode 100644 index 0000000000000..94ddb32acd201 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.tier; + +import org.opensearch.common.cache.store.Cache; +import org.opensearch.common.cache.store.enums.CacheStoreType; + +/** + * This represents a cache comprising of multiple tiers/layers. + * @param Type of key + * @param Type of value + */ +public interface TieredCache extends Cache { + Iterable cacheKeys(CacheStoreType type); + + void refresh(CacheStoreType type); +} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheLoader.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheLoader.java deleted file mode 100644 index d720feade0609..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheLoader.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier; - -/** - * Used to load value in tiered cache if not present. - * @param Type of key - * @param Type of value - */ -public interface TieredCacheLoader { - V load(K key) throws Exception; - - boolean isLoaded(); -} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheRemovalNotification.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheRemovalNotification.java deleted file mode 100644 index d72279144b6ef..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredCacheRemovalNotification.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier; - -import org.opensearch.common.cache.RemovalNotification; -import org.opensearch.common.cache.RemovalReason; -import org.opensearch.common.cache.tier.enums.CacheStoreType; - -/** - * Notification when an element is removed from tiered cache. - * @param Type of key - * @param Type of value - * - * @opensearch.internal - */ -public class TieredCacheRemovalNotification extends RemovalNotification { - private final CacheStoreType cacheStoreType; - - public TieredCacheRemovalNotification(K key, V value, RemovalReason removalReason) { - super(key, value, removalReason); - this.cacheStoreType = CacheStoreType.ON_HEAP; - } - - public TieredCacheRemovalNotification(K key, V value, RemovalReason removalReason, CacheStoreType cacheStoreType) { - super(key, value, removalReason); - this.cacheStoreType = cacheStoreType; - } - - public CacheStoreType getTierType() { - return cacheStoreType; - } -} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java new file mode 100644 index 0000000000000..db340a9205b20 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java @@ -0,0 +1,291 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.tier; + +import org.opensearch.common.cache.LoadAwareCacheLoader; +import org.opensearch.common.cache.RemovalReason; +import org.opensearch.common.cache.store.Cache; +import org.opensearch.common.cache.store.StoreAwareCache; +import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; +import org.opensearch.common.cache.store.StoreAwareCacheValue; +import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; +import org.opensearch.common.cache.store.enums.CacheStoreType; +import org.opensearch.common.cache.store.listeners.EventType; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListener; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; +import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheListenerDispatcherDefaultImpl; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +/** + * This cache spillover the evicted items from upper tier to lower tier. For now, we are spilling the in-memory + * cache items to disk tier cache. All the new items are cached onHeap and if any items are evicted are moved to disk + * cache. + * @param Type of key + * @param Type of value + */ +public class TieredSpilloverCache implements TieredCache, StoreAwareCacheEventListener { + + private final Optional> onDiskCache; + private final StoreAwareCache onHeapCache; + private final StoreAwareCacheListenerDispatcherDefaultImpl eventDispatcher; + + /** + * Maintains caching tiers in order of get calls. + */ + private final List> cacheList; + + TieredSpilloverCache(Builder builder) { + Objects.requireNonNull(builder.onHeapCacheBuilder, "onHeap cache builder can't be null"); + this.onHeapCache = builder.onHeapCacheBuilder.setEventListenerConfiguration(getCacheEventListenerConfiguration()).build(); + if (builder.onDiskCacheBuilder != null) { + this.onDiskCache = Optional.of( + builder.onDiskCacheBuilder.setEventListenerConfiguration(getCacheEventListenerConfiguration()).build() + ); + } else { + this.onDiskCache = Optional.empty(); + } + this.eventDispatcher = new StoreAwareCacheListenerDispatcherDefaultImpl(builder.listenerConfiguration); + this.cacheList = this.onDiskCache.map(diskTier -> Arrays.asList(this.onHeapCache, diskTier)).orElse(List.of(this.onHeapCache)); + } + + private StoreAwareCacheEventListenerConfiguration getCacheEventListenerConfiguration() { + return new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(this) + .setEventTypes(EnumSet.of(EventType.ON_REMOVAL, EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS)) + .build(); + } + + @Override + public V get(K key) { + StoreAwareCacheValue cacheValue = getValueFromTieredCache().apply(key); + if (cacheValue == null) { + return null; + } + return cacheValue.getValue(); + } + + @Override + public void put(K key, V value) { + onHeapCache.put(key, value); + } + + @Override + public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { + StoreAwareCacheValue cacheValue = getValueFromTieredCache().apply(key); + if (cacheValue == null) { + // Add the value to the onHeap cache. Any items if evicted will be moved to lower tier. + V value = onHeapCache.compute(key, loader); + return value; + } + return cacheValue.getValue(); + } + + @Override + public void invalidate(K key) { + // We are trying to invalidate the key from all caches though it would be present in only of them. + // Doing this as we don't where it is located. We could do a get from both and check that, but what will also + // trigger a hit/miss listener event, so ignoring it for now. + for (StoreAwareCache storeAwareCache : cacheList) { + storeAwareCache.invalidate(key); + } + } + + @Override + public V compute(K key, LoadAwareCacheLoader loader) throws Exception { + return onHeapCache.compute(key, loader); + } + + @Override + public void invalidateAll() { + for (StoreAwareCache storeAwareCache : cacheList) { + storeAwareCache.invalidateAll(); + } + } + + @Override + public Iterable keys() { + Iterable onDiskKeysIterable; + if (onDiskCache.isPresent()) { + onDiskKeysIterable = onDiskCache.get().keys(); + } else { + onDiskKeysIterable = Collections::emptyIterator; + } + return new MergedIterable<>(onHeapCache.keys(), onDiskKeysIterable); + } + + @Override + public long count() { + long totalCount = 0; + for (StoreAwareCache storeAwareCache : cacheList) { + totalCount += storeAwareCache.count(); + } + return totalCount; + } + + @Override + public void refresh() { + for (StoreAwareCache storeAwareCache : cacheList) { + storeAwareCache.refresh(); + } + } + + @Override + public Iterable cacheKeys(CacheStoreType type) { + switch (type) { + case ON_HEAP: + return onHeapCache.keys(); + case DISK: + if (onDiskCache.isPresent()) { + return onDiskCache.get().keys(); + } else { + return Collections::emptyIterator; + } + default: + throw new IllegalArgumentException("Unsupported Cache store type: " + type); + } + } + + @Override + public void refresh(CacheStoreType type) { + switch (type) { + case ON_HEAP: + onHeapCache.refresh(); + break; + case DISK: + onDiskCache.ifPresent(Cache::refresh); + break; + default: + throw new IllegalArgumentException("Unsupported Cache store type: " + type); + } + } + + @Override + public void onMiss(K key, CacheStoreType cacheStoreType) { + eventDispatcher.dispatch(key, null, cacheStoreType, EventType.ON_MISS); + } + + @Override + public void onRemoval(StoreAwareCacheRemovalNotification notification) { + if (RemovalReason.EVICTED.equals(notification.getRemovalReason())) { + switch (notification.getCacheStoreType()) { + case ON_HEAP: + onDiskCache.ifPresent(diskTier -> { diskTier.put(notification.getKey(), notification.getValue()); }); + break; + default: + break; + } + } + eventDispatcher.dispatchRemovalEvent(notification); + } + + @Override + public void onHit(K key, V value, CacheStoreType cacheStoreType) { + eventDispatcher.dispatch(key, value, cacheStoreType, EventType.ON_HIT); + } + + @Override + public void onCached(K key, V value, CacheStoreType cacheStoreType) { + eventDispatcher.dispatch(key, value, cacheStoreType, EventType.ON_CACHED); + } + + private Function> getValueFromTieredCache() { + return key -> { + for (StoreAwareCache storeAwareCache : cacheList) { + V value = storeAwareCache.get(key); + if (value != null) { + return new StoreAwareCacheValue<>(value, storeAwareCache.getTierType()); + } + } + return null; + }; + } + + /** + * Builder object for tiered spillover cache. + * @param Type of key + * @param Type of value + */ + public static class Builder { + private StoreAwareCacheBuilder onHeapCacheBuilder; + private StoreAwareCacheBuilder onDiskCacheBuilder; + private StoreAwareCacheEventListenerConfiguration listenerConfiguration; + + public Builder() {} + + public Builder setOnHeapCacheBuilder(StoreAwareCacheBuilder onHeapCacheBuilder) { + this.onHeapCacheBuilder = onHeapCacheBuilder; + return this; + } + + public Builder setOnDiskCacheBuilder(StoreAwareCacheBuilder onDiskCacheBuilder) { + this.onDiskCacheBuilder = onDiskCacheBuilder; + return this; + } + + public Builder setListenerConfiguration(StoreAwareCacheEventListenerConfiguration listenerConfiguration) { + this.listenerConfiguration = listenerConfiguration; + return this; + } + + public TieredSpilloverCache build() { + return new TieredSpilloverCache<>(this); + } + } + + /** + * Returns a merged iterable which can be used to iterate over both onHeap and disk cache keys. + * @param Type of key. + */ + public class MergedIterable implements Iterable { + + private final Iterable onHeapKeysIterable; + private final Iterable onDiskKeysIterable; + + public MergedIterable(Iterable onHeapKeysIterable, Iterable onDiskKeysIterable) { + this.onHeapKeysIterable = onHeapKeysIterable; + this.onDiskKeysIterable = onDiskKeysIterable; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator onHeapIterator = onHeapKeysIterable.iterator(); + private final Iterator onDiskIterator = onDiskKeysIterable.iterator(); + private boolean useOnHeapIterator = true; + + @Override + public boolean hasNext() { + return onHeapIterator.hasNext() || onDiskIterator.hasNext(); + } + + @Override + public K next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + if (useOnHeapIterator && onHeapIterator.hasNext()) { + return onHeapIterator.next(); + } else { + useOnHeapIterator = false; + return onDiskIterator.next(); + } + } + }; + } + } +} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheRemovalListener.java b/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheRemovalListener.java deleted file mode 100644 index 4fcbae7432b64..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/listeners/TieredCacheRemovalListener.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier.listeners; - -import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; - -/** - * Listener for removing an element from tiered cache - * - * @opensearch.internal - */ -@FunctionalInterface -public interface TieredCacheRemovalListener { - void onRemoval(TieredCacheRemovalNotification notification); -} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheService.java b/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheService.java deleted file mode 100644 index 91896b7cfa8e5..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheService.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier.service; - -import org.opensearch.common.cache.tier.DiskCachingTier; -import org.opensearch.common.cache.tier.OnHeapCachingTier; -import org.opensearch.common.cache.tier.TieredCacheLoader; - -import java.util.Optional; - -/** - * This service encapsulates all logic to write/fetch to/from appropriate tiers. Can be implemented with different - * flavors like spillover etc. - * @param Type of key - * @param Type of value - */ -public interface TieredCacheService { - - V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception; - - V get(K key); - - void invalidate(K key); - - void invalidateAll(); - - long count(); - - OnHeapCachingTier getOnHeapCachingTier(); - - Optional> getDiskCachingTier(); -} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java b/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java deleted file mode 100644 index 7b06ae5d3cb16..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/service/TieredCacheSpilloverStrategyService.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier.service; - -import org.opensearch.common.cache.RemovalReason; -import org.opensearch.common.cache.tier.enums.CacheStoreType; -import org.opensearch.common.cache.tier.CachingTier; -import org.opensearch.common.cache.tier.DiskCachingTier; -import org.opensearch.common.cache.tier.OnHeapCachingTier; -import org.opensearch.common.cache.tier.TieredCacheLoader; -import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; -import org.opensearch.common.cache.tier.listeners.TieredCacheEventListener; -import org.opensearch.common.cache.tier.listeners.TieredCacheRemovalListener; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; - -/** - * This service spillover the evicted items from upper tier to lower tier. For now, we are spilling the in-memory - * cache items to disk tier cache. - * @param Type of key - * @param Type of value - */ -public class TieredCacheSpilloverStrategyService implements TieredCacheService, TieredCacheRemovalListener { - - private final OnHeapCachingTier onHeapCachingTier; - - /** - * Optional in case tiered caching is turned off. - */ - private final Optional> diskCachingTier; - private final TieredCacheEventListener tieredCacheEventListener; - - /** - * Maintains caching tiers in order of get calls. - */ - private final List> cachingTierList; - - private TieredCacheSpilloverStrategyService(Builder builder) { - this.onHeapCachingTier = Objects.requireNonNull(builder.onHeapCachingTier); - this.diskCachingTier = Optional.ofNullable(builder.diskCachingTier); - this.tieredCacheEventListener = Objects.requireNonNull(builder.tieredCacheEventListener); - this.cachingTierList = this.diskCachingTier.map(diskTier -> Arrays.asList(onHeapCachingTier, diskTier)) - .orElse(List.of(onHeapCachingTier)); - setRemovalListeners(); - } - - /** - * This method logic is divided into 2 parts: - * 1. First check whether key is present or not in desired tier. If yes, return the value. - * 2. If the key is not present, then add the key/value pair to onHeap cache. - * @param key Key for lookup. - * @param loader Used to load value in case it is not present in any tier. - * @return value - * @throws Exception exception thrown - */ - @Override - public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { - CacheValue cacheValue = getValueFromTieredCache(true).apply(key); - if (cacheValue == null) { - // Add the value to the onHeap cache. Any items if evicted will be moved to lower tier. - V value = onHeapCachingTier.compute(key, loader); - tieredCacheEventListener.onCached(key, value, CacheStoreType.ON_HEAP); - return value; - } - return cacheValue.value; - } - - @Override - public V get(K key) { - CacheValue cacheValue = getValueFromTieredCache(true).apply(key); - if (cacheValue == null) { - return null; - } - return cacheValue.value; - } - - /** - * First fetches the tier type which has this key. And then invalidate accordingly. - * @param key key to invalidate - */ - @Override - public void invalidate(K key) { - // We don't need to track hits/misses in this case. - CacheValue cacheValue = getValueFromTieredCache(false).apply(key); - if (cacheValue != null) { - switch (cacheValue.source) { - case ON_HEAP: - onHeapCachingTier.invalidate(key); - break; - case DISK: - diskCachingTier.ifPresent(diskTier -> diskTier.invalidate(key)); - break; - default: - break; - } - } - } - - @Override - public void invalidateAll() { - for (CachingTier cachingTier : cachingTierList) { - cachingTier.invalidateAll(); - } - } - - /** - * Returns the total count of items present in all cache tiers. - * @return total count of items in cache - */ - @Override - public long count() { - long totalCount = 0; - for (CachingTier cachingTier : cachingTierList) { - totalCount += cachingTier.count(); - } - return totalCount; - } - - /** - * Called whenever an item is evicted from any cache tier. If the item was evicted from onHeap cache, it is moved - * to disk tier cache. In case it was evicted from disk tier cache, it will discarded. - * @param notification Contains info about the removal like reason, key/value etc. - */ - @Override - public void onRemoval(TieredCacheRemovalNotification notification) { - if (RemovalReason.EVICTED.equals(notification.getRemovalReason())) { - switch (notification.getTierType()) { - case ON_HEAP: - diskCachingTier.ifPresent(diskTier -> { - diskTier.put(notification.getKey(), notification.getValue()); - tieredCacheEventListener.onCached(notification.getKey(), notification.getValue(), CacheStoreType.DISK); - }); - break; - default: - break; - } - } - tieredCacheEventListener.onRemoval(notification); - } - - @Override - public OnHeapCachingTier getOnHeapCachingTier() { - return this.onHeapCachingTier; - } - - @Override - public Optional> getDiskCachingTier() { - return this.diskCachingTier; - } - - /** - * Register this service as a listener to removal events from different caching tiers. - */ - private void setRemovalListeners() { - for (CachingTier cachingTier : cachingTierList) { - cachingTier.setRemovalListener(this); - } - } - - private Function> getValueFromTieredCache(boolean trackStats) { - return key -> { - for (CachingTier cachingTier : cachingTierList) { - V value = cachingTier.get(key); - if (value != null) { - if (trackStats) { - tieredCacheEventListener.onHit(key, value, cachingTier.getTierType()); - } - return new CacheValue<>(value, cachingTier.getTierType()); - } - if (trackStats) { - tieredCacheEventListener.onMiss(key, cachingTier.getTierType()); - } - } - return null; - }; - } - - /** - * Represents a cache value along with its associated tier type where it is stored. - * @param Type of value. - */ - public static class CacheValue { - V value; - CacheStoreType source; - - CacheValue(V value, CacheStoreType source) { - this.value = value; - this.source = source; - } - } - - /** - * Builder object - * @param Type of key - * @param Type of value - */ - public static class Builder { - private OnHeapCachingTier onHeapCachingTier; - private DiskCachingTier diskCachingTier; - private TieredCacheEventListener tieredCacheEventListener; - - public Builder() {} - - public Builder setOnHeapCachingTier(OnHeapCachingTier onHeapCachingTier) { - this.onHeapCachingTier = onHeapCachingTier; - return this; - } - - public Builder setOnDiskCachingTier(DiskCachingTier diskCachingTier) { - this.diskCachingTier = diskCachingTier; - return this; - } - - public Builder setTieredCacheEventListener(TieredCacheEventListener tieredCacheEventListener) { - this.tieredCacheEventListener = tieredCacheEventListener; - return this; - } - - public TieredCacheSpilloverStrategyService build() { - return new TieredCacheSpilloverStrategyService(this); - } - } - -} diff --git a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java index ff7619514098c..60d17bbebf8ce 100644 --- a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java +++ b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java @@ -34,7 +34,7 @@ import org.apache.lucene.util.Accountable; import org.opensearch.common.annotation.PublicApi; -import org.opensearch.common.cache.tier.enums.CacheStoreType; +import org.opensearch.common.cache.store.enums.CacheStoreType; import org.opensearch.common.metrics.CounterMetric; import org.opensearch.core.common.bytes.BytesReference; diff --git a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java index 5beb4697190e2..8b77cced0e27d 100644 --- a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java +++ b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java @@ -34,8 +34,8 @@ import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.cache.RemovalReason; -import org.opensearch.common.cache.tier.enums.CacheStoreType; -import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; +import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; +import org.opensearch.common.cache.store.enums.CacheStoreType; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.index.cache.request.ShardRequestCache; import org.opensearch.index.shard.IndexShard; @@ -69,19 +69,15 @@ public final void onMiss(CacheStoreType cacheStoreType) { @Override public final void onRemoval(RemovalNotification notification) { - if (notification instanceof TieredCacheRemovalNotification){ + if (notification instanceof StoreAwareCacheRemovalNotification) { stats().onRemoval( notification.getKey(), notification.getValue(), notification.getRemovalReason() == RemovalReason.EVICTED, - ((TieredCacheRemovalNotification) notification).getTierType() + ((StoreAwareCacheRemovalNotification) notification).getCacheStoreType() ); } else { - stats().onRemoval( - notification.getKey(), - notification.getValue(), - notification.getRemovalReason() == RemovalReason.EVICTED - ); + stats().onRemoval(notification.getKey(), notification.getValue(), notification.getRemovalReason() == RemovalReason.EVICTED); } } } diff --git a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java index d7462e8dc3287..df935ac6e4f8c 100644 --- a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java +++ b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java @@ -39,15 +39,18 @@ import org.apache.lucene.util.Accountable; import org.apache.lucene.util.RamUsageEstimator; import org.opensearch.common.CheckedSupplier; +import org.opensearch.common.cache.LoadAwareCacheLoader; import org.opensearch.common.cache.RemovalNotification; -import org.opensearch.common.cache.tier.OnHeapCachingTier; -import org.opensearch.common.cache.tier.OpenSearchOnHeapCache; -import org.opensearch.common.cache.tier.enums.CacheStoreType; -import org.opensearch.common.cache.tier.listeners.TieredCacheEventListener; -import org.opensearch.common.cache.tier.TieredCacheLoader; -import org.opensearch.common.cache.tier.TieredCacheRemovalNotification; -import org.opensearch.common.cache.tier.service.TieredCacheService; -import org.opensearch.common.cache.tier.service.TieredCacheSpilloverStrategyService; +import org.opensearch.common.cache.store.Cache; +import org.opensearch.common.cache.store.OpenSearchOnHeapCache; +import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; +import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; +import org.opensearch.common.cache.store.enums.CacheStoreType; +import org.opensearch.common.cache.store.listeners.EventType; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListener; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; +import org.opensearch.common.cache.tier.TieredCache; +import org.opensearch.common.cache.tier.TieredSpilloverCache; import org.opensearch.common.lucene.index.OpenSearchDirectoryReader; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Setting.Property; @@ -61,6 +64,7 @@ import java.io.IOException; import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import java.util.Objects; @@ -82,7 +86,7 @@ * * @opensearch.internal */ -public final class IndicesRequestCache implements TieredCacheEventListener, Closeable { +public final class IndicesRequestCache implements StoreAwareCacheEventListener, Closeable { private static final Logger logger = LogManager.getLogger(IndicesRequestCache.class); @@ -111,27 +115,29 @@ public final class IndicesRequestCache implements TieredCacheEventListener keysToClean = ConcurrentCollections.newConcurrentSet(); private final ByteSizeValue size; private final TimeValue expire; - private final TieredCacheService tieredCacheService; + private final Cache cache; IndicesRequestCache(Settings settings) { this.size = INDICES_CACHE_QUERY_SIZE.get(settings); this.expire = INDICES_CACHE_QUERY_EXPIRE.exists(settings) ? INDICES_CACHE_QUERY_EXPIRE.get(settings) : null; long sizeInBytes = size.getBytes(); - - // Initialize onHeap cache tier first. - OnHeapCachingTier openSearchOnHeapCache = new OpenSearchOnHeapCache.Builder().setWeigher( - (k, v) -> k.ramBytesUsed() + v.ramBytesUsed() - ).setMaximumWeight(sizeInBytes).setExpireAfterAccess(expire).build(); - - // Initialize tiered cache service. TODO: Enable Disk tier when tiered support is turned on. - tieredCacheService = new TieredCacheSpilloverStrategyService.Builder().setOnHeapCachingTier( - openSearchOnHeapCache - ).setTieredCacheEventListener(this).build(); + StoreAwareCacheBuilder openSearchOnHeapCacheBuilder = new OpenSearchOnHeapCache.Builder() + .setWeigher((k, v) -> k.ramBytesUsed() + v.ramBytesUsed()) + .setMaximumWeightInBytes(sizeInBytes) + .setExpireAfterAccess(expire); + + cache = new TieredSpilloverCache.Builder().setOnHeapCacheBuilder(openSearchOnHeapCacheBuilder) + .setListenerConfiguration( + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(this) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build() + ) + .build(); } @Override public void close() { - tieredCacheService.invalidateAll(); + cache.invalidateAll(); } void clear(CacheEntity entity) { @@ -145,7 +151,7 @@ public void onMiss(Key key, CacheStoreType cacheStoreType) { } @Override - public void onRemoval(TieredCacheRemovalNotification notification) { + public void onRemoval(StoreAwareCacheRemovalNotification notification) { notification.getKey().entity.onRemoval(notification); } @@ -168,7 +174,7 @@ BytesReference getOrCompute( assert reader.getReaderCacheHelper() != null; final Key key = new Key(cacheEntity, reader.getReaderCacheHelper().getKey(), cacheKey); Loader cacheLoader = new Loader(cacheEntity, loader); - BytesReference value = tieredCacheService.computeIfAbsent(key, cacheLoader); + BytesReference value = cache.computeIfAbsent(key, cacheLoader); if (cacheLoader.isLoaded()) { // see if its the first time we see this reader, and make sure to register a cleanup key CleanupKey cleanupKey = new CleanupKey(cacheEntity, reader.getReaderCacheHelper().getKey()); @@ -190,7 +196,7 @@ BytesReference getOrCompute( */ void invalidate(CacheEntity cacheEntity, DirectoryReader reader, BytesReference cacheKey) { assert reader.getReaderCacheHelper() != null; - tieredCacheService.invalidate(new Key(cacheEntity, reader.getReaderCacheHelper().getKey(), cacheKey)); + cache.invalidate(new Key(cacheEntity, reader.getReaderCacheHelper().getKey(), cacheKey)); } /** @@ -198,7 +204,7 @@ void invalidate(CacheEntity cacheEntity, DirectoryReader reader, BytesReference * * @opensearch.internal */ - private static class Loader implements TieredCacheLoader { + private static class Loader implements LoadAwareCacheLoader { private final CacheEntity entity; private final CheckedSupplier loader; @@ -364,7 +370,7 @@ synchronized void cleanCache() { } } if (!currentKeysToClean.isEmpty() || !currentFullClean.isEmpty()) { - for (Iterator iterator = tieredCacheService.getOnHeapCachingTier().keys().iterator(); iterator.hasNext();) { + for (Iterator iterator = getIterable(CacheStoreType.ON_HEAP).iterator(); iterator.hasNext();) { Key key = iterator.next(); if (currentFullClean.contains(key.entity.getCacheIdentity())) { iterator.remove(); @@ -375,14 +381,30 @@ synchronized void cleanCache() { } } } - tieredCacheService.getOnHeapCachingTier().refresh(); + refresh(CacheStoreType.ON_HEAP); + } + + private Iterable getIterable(CacheStoreType cacheStoreType) { + if (cache instanceof TieredCache) { + return ((TieredCache) cache).cacheKeys(cacheStoreType); + } else { + return cache.keys(); + } + } + + private void refresh(CacheStoreType cacheStoreType) { + if (cache instanceof TieredCache) { + ((TieredCache) cache).refresh(cacheStoreType); + } else { + cache.refresh(); + } } /** * Returns the current size of the cache */ long count() { - return tieredCacheService.count(); + return cache.count(); } int numRegisteredCloseListeners() { // for testing diff --git a/server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java deleted file mode 100644 index a10db9bb43f85..0000000000000 --- a/server/src/test/java/org/opensearch/common/cache/tier/TieredCacheSpilloverStrategyServiceTests.java +++ /dev/null @@ -1,460 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier; - -import org.opensearch.common.cache.RemovalReason; -import org.opensearch.common.cache.tier.enums.CacheStoreType; -import org.opensearch.common.cache.tier.listeners.TieredCacheEventListener; -import org.opensearch.common.cache.tier.listeners.TieredCacheRemovalListener; -import org.opensearch.common.cache.tier.service.TieredCacheSpilloverStrategyService; -import org.opensearch.common.metrics.CounterMetric; -import org.opensearch.test.OpenSearchTestCase; - -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -public class TieredCacheSpilloverStrategyServiceTests extends OpenSearchTestCase { - - public void testComputeAndAbsentWithoutAnyOnHeapCacheEviction() throws Exception { - int onHeapCacheSize = randomIntBetween(10, 30); - MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); - TieredCacheSpilloverStrategyService spilloverStrategyService = intializeTieredCacheService( - onHeapCacheSize, - randomIntBetween(1, 4), - eventListener - ); - int numOfItems1 = randomIntBetween(1, onHeapCacheSize / 2 - 1); - List keys = new ArrayList<>(); - // Put values in cache. - for (int iter = 0; iter < numOfItems1; iter++) { - String key = UUID.randomUUID().toString(); - keys.add(key); - TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); - spilloverStrategyService.computeIfAbsent(key, tieredCacheLoader); - } - assertEquals(numOfItems1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); - assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); - assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count()); - - // Try to hit cache again with some randomization. - int numOfItems2 = randomIntBetween(1, onHeapCacheSize / 2 - 1); - int cacheHit = 0; - int cacheMiss = 0; - for (int iter = 0; iter < numOfItems2; iter++) { - if (randomBoolean()) { - // Hit cache with stored key - cacheHit++; - int index = randomIntBetween(0, keys.size() - 1); - spilloverStrategyService.computeIfAbsent(keys.get(index), getTieredCacheLoader()); - } else { - // Hit cache with randomized key which is expected to miss cache always. - spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), getTieredCacheLoader()); - cacheMiss++; - } - } - assertEquals(cacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); - assertEquals(numOfItems1 + cacheMiss, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); - assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count()); - } - - public void testComputeAndAbsentWithEvictionsFromOnHeapCache() throws Exception { - int onHeapCacheSize = randomIntBetween(10, 30); - int diskCacheSize = randomIntBetween(60, 100); - int totalSize = onHeapCacheSize + diskCacheSize; - MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); - TieredCacheSpilloverStrategyService spilloverStrategyService = intializeTieredCacheService( - onHeapCacheSize, - diskCacheSize, - eventListener - ); - - // Put values in cache more than it's size and cause evictions from onHeap. - int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); - List onHeapKeys = new ArrayList<>(); - List diskTierKeys = new ArrayList<>(); - for (int iter = 0; iter < numOfItems1; iter++) { - String key = UUID.randomUUID().toString(); - if (iter > (onHeapCacheSize - 1)) { - // All these are bound to go to disk based cache. - diskTierKeys.add(key); - } else { - onHeapKeys.add(key); - } - TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); - spilloverStrategyService.computeIfAbsent(key, tieredCacheLoader); - } - assertEquals(numOfItems1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); - assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); - assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); - - assertEquals( - eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count(), - eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count() - ); - assertEquals(diskTierKeys.size(), eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count()); - - // Try to hit cache again with some randomization. - int numOfItems2 = randomIntBetween(50, 200); - int onHeapCacheHit = 0; - int diskCacheHit = 0; - int cacheMiss = 0; - for (int iter = 0; iter < numOfItems2; iter++) { - if (randomBoolean()) { - if (randomBoolean()) { // Hit cache with key stored in onHeap cache. - onHeapCacheHit++; - int index = randomIntBetween(0, onHeapKeys.size() - 1); - spilloverStrategyService.computeIfAbsent(onHeapKeys.get(index), getTieredCacheLoader()); - } else { // Hit cache with key stored in disk cache. - diskCacheHit++; - int index = randomIntBetween(0, diskTierKeys.size() - 1); - spilloverStrategyService.computeIfAbsent(diskTierKeys.get(index), getTieredCacheLoader()); - } - } else { - // Hit cache with randomized key which is expected to miss cache always. - TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); - spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); - cacheMiss++; - } - } - // On heap cache misses would also include diskCacheHits as it means it missed onHeap cache. - assertEquals(numOfItems1 + cacheMiss + diskCacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); - assertEquals(onHeapCacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); - assertEquals(cacheMiss + numOfItems1, eventListener.enumMap.get(CacheStoreType.DISK).missCount.count()); - assertEquals(diskCacheHit, eventListener.enumMap.get(CacheStoreType.DISK).hitCount.count()); - } - - public void testComputeAndAbsentWithEvictionsFromBothTier() throws Exception { - int onHeapCacheSize = randomIntBetween(10, 30); - int diskCacheSize = randomIntBetween(onHeapCacheSize + 1, 100); - int totalSize = onHeapCacheSize + diskCacheSize; - - MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); - TieredCacheSpilloverStrategyService spilloverStrategyService = intializeTieredCacheService( - onHeapCacheSize, - diskCacheSize, - eventListener - ); - - int numOfItems = randomIntBetween(totalSize + 1, totalSize * 3); - for (int iter = 0; iter < numOfItems; iter++) { - TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); - spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); - } - assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); - assertTrue(eventListener.enumMap.get(CacheStoreType.DISK).evictionsMetric.count() > 0); - } - - public void testGetAndCount() throws Exception { - int onHeapCacheSize = randomIntBetween(10, 30); - int diskCacheSize = randomIntBetween(onHeapCacheSize + 1, 100); - int totalSize = onHeapCacheSize + diskCacheSize; - - MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); - TieredCacheSpilloverStrategyService spilloverStrategyService = intializeTieredCacheService( - onHeapCacheSize, - diskCacheSize, - eventListener - ); - - int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); - List onHeapKeys = new ArrayList<>(); - List diskTierKeys = new ArrayList<>(); - for (int iter = 0; iter < numOfItems1; iter++) { - String key = UUID.randomUUID().toString(); - if (iter > (onHeapCacheSize - 1)) { - // All these are bound to go to disk based cache. - diskTierKeys.add(key); - } else { - onHeapKeys.add(key); - } - TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); - spilloverStrategyService.computeIfAbsent(key, tieredCacheLoader); - } - - for (int iter = 0; iter < numOfItems1; iter++) { - if (randomBoolean()) { - if (randomBoolean()) { - int index = randomIntBetween(0, onHeapKeys.size() - 1); - assertNotNull(spilloverStrategyService.get(onHeapKeys.get(index))); - } else { - int index = randomIntBetween(0, diskTierKeys.size() - 1); - assertNotNull(spilloverStrategyService.get(diskTierKeys.get(index))); - } - } else { - assertNull(spilloverStrategyService.get(UUID.randomUUID().toString())); - } - } - assertEquals(numOfItems1, spilloverStrategyService.count()); - } - - public void testWithDiskTierNull() throws Exception { - int onHeapCacheSize = randomIntBetween(10, 30); - MockTieredCacheEventListener eventListener = new MockTieredCacheEventListener(); - TieredCacheSpilloverStrategyService spilloverStrategyService = new TieredCacheSpilloverStrategyService.Builder< - String, - String>().setOnHeapCachingTier(new MockOnHeapCacheTier<>(onHeapCacheSize)).setTieredCacheEventListener(eventListener).build(); - int numOfItems = randomIntBetween(onHeapCacheSize + 1, onHeapCacheSize * 3); - for (int iter = 0; iter < numOfItems; iter++) { - TieredCacheLoader tieredCacheLoader = getTieredCacheLoader(); - spilloverStrategyService.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); - } - assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); - assertEquals(0, eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count()); - assertEquals(0, eventListener.enumMap.get(CacheStoreType.DISK).evictionsMetric.count()); - assertEquals(0, eventListener.enumMap.get(CacheStoreType.DISK).missCount.count()); - } - - private TieredCacheLoader getTieredCacheLoader() { - return new TieredCacheLoader() { - boolean isLoaded = false; - - @Override - public String load(String key) { - isLoaded = true; - return UUID.randomUUID().toString(); - } - - @Override - public boolean isLoaded() { - return isLoaded; - } - }; - } - - private TieredCacheSpilloverStrategyService intializeTieredCacheService( - int onHeapCacheSize, - int diksCacheSize, - TieredCacheEventListener cacheEventListener - ) { - DiskCachingTier diskCache = new MockDiskCachingTier<>(diksCacheSize); - OnHeapCachingTier openSearchOnHeapCache = new MockOnHeapCacheTier<>(onHeapCacheSize); - return new TieredCacheSpilloverStrategyService.Builder().setOnHeapCachingTier(openSearchOnHeapCache) - .setOnDiskCachingTier(diskCache) - .setTieredCacheEventListener(cacheEventListener) - .build(); - } - - class MockOnHeapCacheTier implements OnHeapCachingTier, TieredCacheRemovalListener { - - Map onHeapCacheTier; - int maxSize; - private TieredCacheRemovalListener removalListener; - - MockOnHeapCacheTier(int size) { - maxSize = size; - this.onHeapCacheTier = new ConcurrentHashMap(); - } - - @Override - public V get(K key) { - return this.onHeapCacheTier.get(key); - } - - @Override - public void put(K key, V value) { - this.onHeapCacheTier.put(key, value); - } - - @Override - public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { - if (this.onHeapCacheTier.size() > maxSize) { // If it exceeds, just notify for evict. - onRemoval(new TieredCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP)); - return loader.load(key); - } - return this.onHeapCacheTier.computeIfAbsent(key, k -> { - try { - return loader.load(key); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - @Override - public void invalidate(K key) { - this.onHeapCacheTier.remove(key); - } - - @Override - public V compute(K key, TieredCacheLoader loader) throws Exception { - if (this.onHeapCacheTier.size() >= maxSize) { // If it exceeds, just notify for evict. - onRemoval(new TieredCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP)); - return loader.load(key); - } - return this.onHeapCacheTier.compute(key, ((k, v) -> { - try { - return loader.load(key); - } catch (Exception e) { - throw new RuntimeException(e); - } - })); - } - - @Override - public void setRemovalListener(TieredCacheRemovalListener removalListener) { - this.removalListener = removalListener; - } - - @Override - public void invalidateAll() { - this.onHeapCacheTier.clear(); - } - - @Override - public Iterable keys() { - return this.onHeapCacheTier.keySet(); - } - - @Override - public int count() { - return this.onHeapCacheTier.size(); - } - - @Override - public CacheStoreType getTierType() { - return CacheStoreType.ON_HEAP; - } - - @Override - public void onRemoval(TieredCacheRemovalNotification notification) { - removalListener.onRemoval(notification); - } - } - - class MockTieredCacheEventListener implements TieredCacheEventListener { - - EnumMap enumMap = new EnumMap<>(CacheStoreType.class); - - MockTieredCacheEventListener() { - for (CacheStoreType cacheStoreType : CacheStoreType.values()) { - enumMap.put(cacheStoreType, new TestStatsHolder()); - } - } - - @Override - public void onMiss(K key, CacheStoreType cacheStoreType) { - enumMap.get(cacheStoreType).missCount.inc(); - } - - @Override - public void onRemoval(TieredCacheRemovalNotification notification) { - if (notification.getRemovalReason().equals(RemovalReason.EVICTED)) { - enumMap.get(notification.getTierType()).evictionsMetric.inc(); - } - } - - @Override - public void onHit(K key, V value, CacheStoreType cacheStoreType) { - enumMap.get(cacheStoreType).hitCount.inc(); - } - - @Override - public void onCached(K key, V value, CacheStoreType cacheStoreType) { - enumMap.get(cacheStoreType).cachedCount.inc(); - } - - class TestStatsHolder { - final CounterMetric evictionsMetric = new CounterMetric(); - final CounterMetric hitCount = new CounterMetric(); - final CounterMetric missCount = new CounterMetric(); - - final CounterMetric cachedCount = new CounterMetric(); - } - } - - class MockDiskCachingTier implements DiskCachingTier, TieredCacheRemovalListener { - Map diskTier; - private TieredCacheRemovalListener removalListener; - int maxSize; - - MockDiskCachingTier(int size) { - this.maxSize = size; - diskTier = new ConcurrentHashMap(); - } - - @Override - public V get(K key) { - return this.diskTier.get(key); - } - - @Override - public void put(K key, V value) { - if (this.diskTier.size() >= maxSize) { // For simplification - onRemoval(new TieredCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.DISK)); - return; - } - this.diskTier.put(key, value); - } - - @Override - public V computeIfAbsent(K key, TieredCacheLoader loader) throws Exception { - return this.diskTier.computeIfAbsent(key, k -> { - try { - return loader.load(k); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - @Override - public void invalidate(K key) { - this.diskTier.remove(key); - } - - @Override - public V compute(K key, TieredCacheLoader loader) throws Exception { - if (this.diskTier.size() >= maxSize) { // If it exceeds, just notify for evict. - onRemoval(new TieredCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.DISK)); - return loader.load(key); - } - return this.diskTier.compute(key, (k, v) -> { - try { - return loader.load(key); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } - - @Override - public void setRemovalListener(TieredCacheRemovalListener removalListener) { - this.removalListener = removalListener; - } - - @Override - public void invalidateAll() { - this.diskTier.clear(); - } - - @Override - public Iterable keys() { - return null; - } - - @Override - public int count() { - return this.diskTier.size(); - } - - @Override - public CacheStoreType getTierType() { - return CacheStoreType.DISK; - } - - @Override - public void onRemoval(TieredCacheRemovalNotification notification) { - this.removalListener.onRemoval(notification); - } - } -} diff --git a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java new file mode 100644 index 0000000000000..152b74732ccb1 --- /dev/null +++ b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java @@ -0,0 +1,769 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.common.cache.tier; + +import org.opensearch.common.cache.LoadAwareCacheLoader; +import org.opensearch.common.cache.RemovalReason; +import org.opensearch.common.cache.store.StoreAwareCache; +import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; +import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; +import org.opensearch.common.cache.store.enums.CacheStoreType; +import org.opensearch.common.cache.store.listeners.EventType; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListener; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; +import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheEventListenerDispatcher; +import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheListenerDispatcherDefaultImpl; +import org.opensearch.common.metrics.CounterMetric; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class TieredSpilloverCacheTests extends OpenSearchTestCase { + + public void testComputeIfAbsentWithoutAnyOnHeapCacheEviction() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + MockCacheEventListener eventListener = new MockCacheEventListener(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + randomIntBetween(1, 4), + eventListenerConfiguration + ); + int numOfItems1 = randomIntBetween(1, onHeapCacheSize / 2 - 1); + List keys = new ArrayList<>(); + // Put values in cache. + for (int iter = 0; iter < numOfItems1; iter++) { + String key = UUID.randomUUID().toString(); + keys.add(key); + LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(key, tieredCacheLoader); + } + assertEquals(numOfItems1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count()); + + // Try to hit cache again with some randomization. + int numOfItems2 = randomIntBetween(1, onHeapCacheSize / 2 - 1); + int cacheHit = 0; + int cacheMiss = 0; + for (int iter = 0; iter < numOfItems2; iter++) { + if (randomBoolean()) { + // Hit cache with stored key + cacheHit++; + int index = randomIntBetween(0, keys.size() - 1); + tieredSpilloverCache.computeIfAbsent(keys.get(index), getLoadAwareCacheLoader()); + } else { + // Hit cache with randomized key which is expected to miss cache always. + tieredSpilloverCache.computeIfAbsent(UUID.randomUUID().toString(), getLoadAwareCacheLoader()); + cacheMiss++; + } + } + assertEquals(cacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); + assertEquals(numOfItems1 + cacheMiss, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count()); + } + + public void testComputeIfAbsentWithEvictionsFromOnHeapCache() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(60, 100); + int totalSize = onHeapCacheSize + diskCacheSize; + MockCacheEventListener eventListener = new MockCacheEventListener(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListenerConfiguration + ); + + // Put values in cache more than it's size and cause evictions from onHeap. + int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); + List onHeapKeys = new ArrayList<>(); + List diskTierKeys = new ArrayList<>(); + for (int iter = 0; iter < numOfItems1; iter++) { + String key = UUID.randomUUID().toString(); + if (iter > (onHeapCacheSize - 1)) { + // All these are bound to go to disk based cache. + diskTierKeys.add(key); + } else { + onHeapKeys.add(key); + } + LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(key, tieredCacheLoader); + } + assertEquals(numOfItems1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); + assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); + + assertEquals( + eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count(), + eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count() + ); + assertEquals(diskTierKeys.size(), eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count()); + + // Try to hit cache again with some randomization. + int numOfItems2 = randomIntBetween(50, 200); + int onHeapCacheHit = 0; + int diskCacheHit = 0; + int cacheMiss = 0; + for (int iter = 0; iter < numOfItems2; iter++) { + if (randomBoolean()) { + if (randomBoolean()) { // Hit cache with key stored in onHeap cache. + onHeapCacheHit++; + int index = randomIntBetween(0, onHeapKeys.size() - 1); + tieredSpilloverCache.computeIfAbsent(onHeapKeys.get(index), getLoadAwareCacheLoader()); + } else { // Hit cache with key stored in disk cache. + diskCacheHit++; + int index = randomIntBetween(0, diskTierKeys.size() - 1); + tieredSpilloverCache.computeIfAbsent(diskTierKeys.get(index), getLoadAwareCacheLoader()); + } + } else { + // Hit cache with randomized key which is expected to miss cache always. + LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); + cacheMiss++; + } + } + // On heap cache misses would also include diskCacheHits as it means it missed onHeap cache. + assertEquals(numOfItems1 + cacheMiss + diskCacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); + assertEquals(onHeapCacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); + assertEquals(cacheMiss + numOfItems1, eventListener.enumMap.get(CacheStoreType.DISK).missCount.count()); + assertEquals(diskCacheHit, eventListener.enumMap.get(CacheStoreType.DISK).hitCount.count()); + } + + public void testComputeIfAbsentWithEvictionsFromBothTier() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(onHeapCacheSize + 1, 100); + int totalSize = onHeapCacheSize + diskCacheSize; + + MockCacheEventListener eventListener = new MockCacheEventListener(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListenerConfiguration + ); + + int numOfItems = randomIntBetween(totalSize + 1, totalSize * 3); + for (int iter = 0; iter < numOfItems; iter++) { + LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); + } + assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); + assertTrue(eventListener.enumMap.get(CacheStoreType.DISK).evictionsMetric.count() > 0); + } + + public void testGetAndCount() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(onHeapCacheSize + 1, 100); + int totalSize = onHeapCacheSize + diskCacheSize; + + MockCacheEventListener eventListener = new MockCacheEventListener(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListenerConfiguration + ); + + int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); + List onHeapKeys = new ArrayList<>(); + List diskTierKeys = new ArrayList<>(); + for (int iter = 0; iter < numOfItems1; iter++) { + String key = UUID.randomUUID().toString(); + if (iter > (onHeapCacheSize - 1)) { + // All these are bound to go to disk based cache. + diskTierKeys.add(key); + } else { + onHeapKeys.add(key); + } + LoadAwareCacheLoader loadAwareCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(key, loadAwareCacheLoader); + } + + for (int iter = 0; iter < numOfItems1; iter++) { + if (randomBoolean()) { + if (randomBoolean()) { + int index = randomIntBetween(0, onHeapKeys.size() - 1); + assertNotNull(tieredSpilloverCache.get(onHeapKeys.get(index))); + } else { + int index = randomIntBetween(0, diskTierKeys.size() - 1); + assertNotNull(tieredSpilloverCache.get(diskTierKeys.get(index))); + } + } else { + assertNull(tieredSpilloverCache.get(UUID.randomUUID().toString())); + } + } + assertEquals(numOfItems1, tieredSpilloverCache.count()); + } + + public void testWithDiskTierNull() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + MockCacheEventListener eventListener = new MockCacheEventListener(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + + StoreAwareCacheBuilder onHeapCacheBuilder = new MockOnHeapCache.Builder().setMaxSize( + onHeapCacheSize + ); + TieredSpilloverCache tieredSpilloverCache = new TieredSpilloverCache.Builder() + .setOnHeapCacheBuilder(onHeapCacheBuilder) + .setListenerConfiguration(eventListenerConfiguration) + .build(); + + int numOfItems = randomIntBetween(onHeapCacheSize + 1, onHeapCacheSize * 3); + for (int iter = 0; iter < numOfItems; iter++) { + LoadAwareCacheLoader loadAwareCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(UUID.randomUUID().toString(), loadAwareCacheLoader); + } + assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.DISK).evictionsMetric.count()); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.DISK).missCount.count()); + } + + public void testPut() { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(onHeapCacheSize + 1, 100); + + MockCacheEventListener eventListener = new MockCacheEventListener<>(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListenerConfiguration + ); + String key = UUID.randomUUID().toString(); + String value = UUID.randomUUID().toString(); + tieredSpilloverCache.put(key, value); + assertEquals(1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).cachedCount.count()); + assertEquals(1, tieredSpilloverCache.count()); + } + + public void testInvalidate() { + int onHeapCacheSize = 1; + int diskCacheSize = 10; + + MockCacheEventListener eventListener = new MockCacheEventListener<>(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListenerConfiguration + ); + String key = UUID.randomUUID().toString(); + String value = UUID.randomUUID().toString(); + // First try to invalidate without the key present in cache. + tieredSpilloverCache.invalidate(key); + assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).invalidationMetric.count()); + + // Now try to invalidate with the key present in onHeap cache. + tieredSpilloverCache.put(key, value); + tieredSpilloverCache.invalidate(key); + assertEquals(1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).invalidationMetric.count()); + assertEquals(0, tieredSpilloverCache.count()); + + tieredSpilloverCache.put(key, value); + // Put another key/value so that one of the item is evicted to disk cache. + String key2 = UUID.randomUUID().toString(); + tieredSpilloverCache.put(key2, UUID.randomUUID().toString()); + assertEquals(2, tieredSpilloverCache.count()); + // Again invalidate older key + tieredSpilloverCache.invalidate(key2); + assertEquals(1, eventListener.enumMap.get(CacheStoreType.DISK).invalidationMetric.count()); + assertEquals(1, tieredSpilloverCache.count()); + } + + public void testComputeWithoutAnyOnHeapEvictions() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + MockCacheEventListener eventListener = new MockCacheEventListener(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache spilloverStrategyService = intializeTieredSpilloverCache( + onHeapCacheSize, + randomIntBetween(1, 4), + eventListenerConfiguration + ); + int numOfItems1 = randomIntBetween(1, onHeapCacheSize / 2 - 1); + List keys = new ArrayList<>(); + // Put values in cache. + for (int iter = 0; iter < numOfItems1; iter++) { + String key = UUID.randomUUID().toString(); + keys.add(key); + LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); + spilloverStrategyService.compute(key, tieredCacheLoader); + } + assertEquals(numOfItems1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).cachedCount.count()); + } + + public void testCacheKeys() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(60, 100); + int totalSize = onHeapCacheSize + diskCacheSize; + + MockCacheEventListener eventListener = new MockCacheEventListener<>(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListenerConfiguration + ); + // Put values in cache more than it's size and cause evictions from onHeap. + int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); + List onHeapKeys = new ArrayList<>(); + List diskTierKeys = new ArrayList<>(); + for (int iter = 0; iter < numOfItems1; iter++) { + String key = UUID.randomUUID().toString(); + if (iter > (onHeapCacheSize - 1)) { + // All these are bound to go to disk based cache. + diskTierKeys.add(key); + } else { + onHeapKeys.add(key); + } + LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(key, tieredCacheLoader); + } + List actualOnHeapKeys = new ArrayList<>(); + List actualOnDiskKeys = new ArrayList<>(); + Iterable onHeapiterable = tieredSpilloverCache.cacheKeys(CacheStoreType.ON_HEAP); + Iterable onDiskiterable = tieredSpilloverCache.cacheKeys(CacheStoreType.DISK); + onHeapiterable.iterator().forEachRemaining(actualOnHeapKeys::add); + onDiskiterable.iterator().forEachRemaining(actualOnDiskKeys::add); + for (String onHeapKey : onHeapKeys) { + assertTrue(actualOnHeapKeys.contains(onHeapKey)); + } + for (String onDiskKey : actualOnDiskKeys) { + assertTrue(actualOnDiskKeys.contains(onDiskKey)); + } + + // Testing keys() which returns all keys. + List actualMergedKeys = new ArrayList<>(); + List expectedMergedKeys = new ArrayList<>(); + expectedMergedKeys.addAll(onHeapKeys); + expectedMergedKeys.addAll(diskTierKeys); + + Iterable mergedIterable = tieredSpilloverCache.keys(); + mergedIterable.iterator().forEachRemaining(actualMergedKeys::add); + + assertEquals(expectedMergedKeys.size(), actualMergedKeys.size()); + for (String key : expectedMergedKeys) { + assertTrue(actualMergedKeys.contains(key)); + } + } + + public void testRefresh() { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(60, 100); + + MockCacheEventListener eventListener = new MockCacheEventListener<>(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListenerConfiguration + ); + tieredSpilloverCache.refresh(CacheStoreType.ON_HEAP); + tieredSpilloverCache.refresh(CacheStoreType.DISK); + tieredSpilloverCache.refresh(); + } + + public void testInvalidateAll() throws Exception { + int onHeapCacheSize = randomIntBetween(10, 30); + int diskCacheSize = randomIntBetween(60, 100); + int totalSize = onHeapCacheSize + diskCacheSize; + + MockCacheEventListener eventListener = new MockCacheEventListener<>(); + StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = + new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) + .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) + .build(); + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListenerConfiguration + ); + // Put values in cache more than it's size and cause evictions from onHeap. + int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); + List onHeapKeys = new ArrayList<>(); + List diskTierKeys = new ArrayList<>(); + for (int iter = 0; iter < numOfItems1; iter++) { + String key = UUID.randomUUID().toString(); + if (iter > (onHeapCacheSize - 1)) { + // All these are bound to go to disk based cache. + diskTierKeys.add(key); + } else { + onHeapKeys.add(key); + } + LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(key, tieredCacheLoader); + } + assertEquals(numOfItems1, tieredSpilloverCache.count()); + tieredSpilloverCache.invalidateAll(); + assertEquals(0, tieredSpilloverCache.count()); + } + + class MockCacheEventListener implements StoreAwareCacheEventListener { + + EnumMap enumMap = new EnumMap<>(CacheStoreType.class); + + MockCacheEventListener() { + for (CacheStoreType cacheStoreType : CacheStoreType.values()) { + enumMap.put(cacheStoreType, new TestStatsHolder()); + } + } + + @Override + public void onMiss(K key, CacheStoreType cacheStoreType) { + enumMap.get(cacheStoreType).missCount.inc(); + } + + @Override + public void onRemoval(StoreAwareCacheRemovalNotification notification) { + if (notification.getRemovalReason().equals(RemovalReason.EVICTED)) { + enumMap.get(notification.getCacheStoreType()).evictionsMetric.inc(); + } else if (notification.getRemovalReason().equals(RemovalReason.INVALIDATED)) { + enumMap.get(notification.getCacheStoreType()).invalidationMetric.inc(); + } + } + + @Override + public void onHit(K key, V value, CacheStoreType cacheStoreType) { + enumMap.get(cacheStoreType).hitCount.inc(); + } + + @Override + public void onCached(K key, V value, CacheStoreType cacheStoreType) { + enumMap.get(cacheStoreType).cachedCount.inc(); + } + + class TestStatsHolder { + final CounterMetric evictionsMetric = new CounterMetric(); + final CounterMetric hitCount = new CounterMetric(); + final CounterMetric missCount = new CounterMetric(); + final CounterMetric cachedCount = new CounterMetric(); + final CounterMetric invalidationMetric = new CounterMetric(); + } + } + + private LoadAwareCacheLoader getLoadAwareCacheLoader() { + return new LoadAwareCacheLoader() { + boolean isLoaded = false; + + @Override + public String load(String key) { + isLoaded = true; + return UUID.randomUUID().toString(); + } + + @Override + public boolean isLoaded() { + return isLoaded; + } + }; + } + + private TieredSpilloverCache intializeTieredSpilloverCache( + int onHeapCacheSize, + int diksCacheSize, + StoreAwareCacheEventListenerConfiguration listenerConfiguration + ) { + StoreAwareCacheBuilder diskCacheBuilder = new MockOnDiskCache.Builder().setMaxSize(diksCacheSize); + StoreAwareCacheBuilder onHeapCacheBuilder = new MockOnHeapCache.Builder().setMaxSize( + onHeapCacheSize + ); + return new TieredSpilloverCache.Builder().setOnHeapCacheBuilder(onHeapCacheBuilder) + .setOnDiskCacheBuilder(diskCacheBuilder) + .setListenerConfiguration(listenerConfiguration) + .build(); + } +} + +class MockOnDiskCache implements StoreAwareCache { + + Map cache; + int maxSize; + StoreAwareCacheEventListenerDispatcher eventDispatcher; + + MockOnDiskCache(int maxSize, StoreAwareCacheEventListenerConfiguration eventListenerConfiguration) { + this.maxSize = maxSize; + this.eventDispatcher = new StoreAwareCacheListenerDispatcherDefaultImpl<>(eventListenerConfiguration); + this.cache = new ConcurrentHashMap(); + } + + @Override + public V get(K key) { + V value = cache.get(key); + if (value != null) { + eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_HIT); + } else { + eventDispatcher.dispatch(key, null, CacheStoreType.DISK, EventType.ON_MISS); + } + return value; + } + + @Override + public void put(K key, V value) { + if (this.cache.size() >= maxSize) { // For simplification + eventDispatcher.dispatchRemovalEvent( + new StoreAwareCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.DISK) + ); + return; + } + this.cache.put(key, value); + eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_CACHED); + } + + @Override + public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { + V value = cache.computeIfAbsent(key, key1 -> { + try { + return loader.load(key); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + if (!loader.isLoaded()) { + eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_HIT); + } else { + eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_MISS); + eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_CACHED); + } + return value; + } + + @Override + public void invalidate(K key) { + if (this.cache.containsKey(key)) { + eventDispatcher.dispatchRemovalEvent( + new StoreAwareCacheRemovalNotification<>(key, null, RemovalReason.INVALIDATED, CacheStoreType.DISK) + ); + } + this.cache.remove(key); + } + + @Override + public V compute(K key, LoadAwareCacheLoader loader) throws Exception { + if (this.cache.size() >= maxSize) { // If it exceeds, just notify for evict. + eventDispatcher.dispatchRemovalEvent( + new StoreAwareCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.DISK) + ); + return loader.load(key); + } + V value = this.cache.compute(key, ((k, v) -> { + try { + return loader.load(key); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_CACHED); + return value; + } + + @Override + public void invalidateAll() { + this.cache.clear(); + } + + @Override + public Iterable keys() { + return this.cache.keySet(); + } + + @Override + public long count() { + return this.cache.size(); + } + + @Override + public void refresh() {} + + @Override + public CacheStoreType getTierType() { + return CacheStoreType.DISK; + } + + public static class Builder extends StoreAwareCacheBuilder { + + int maxSize; + + @Override + public StoreAwareCache build() { + return new MockOnDiskCache(maxSize, this.getListenerConfiguration()); + } + + public Builder setMaxSize(int maxSize) { + this.maxSize = maxSize; + return this; + } + } +} + +class MockOnHeapCache implements StoreAwareCache { + + Map cache; + int maxSize; + StoreAwareCacheEventListenerDispatcher eventDispatcher; + + MockOnHeapCache(int size, StoreAwareCacheEventListenerConfiguration eventListenerConfiguration) { + maxSize = size; + this.cache = new ConcurrentHashMap(); + this.eventDispatcher = new StoreAwareCacheListenerDispatcherDefaultImpl<>(eventListenerConfiguration); + } + + @Override + public V get(K key) { + V value = cache.get(key); + if (value != null) { + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_HIT); + } else { + eventDispatcher.dispatch(key, null, CacheStoreType.ON_HEAP, EventType.ON_MISS); + } + return value; + } + + @Override + public void put(K key, V value) { + if (this.cache.size() >= maxSize) { + eventDispatcher.dispatchRemovalEvent( + new StoreAwareCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.ON_HEAP) + ); + return; + } + this.cache.put(key, value); + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + } + + @Override + public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { + if (this.cache.size() >= maxSize) { // If it exceeds, just notify for evict. + eventDispatcher.dispatchRemovalEvent( + new StoreAwareCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP) + ); + return loader.load(key); + } + V value = cache.computeIfAbsent(key, key1 -> { + try { + return loader.load(key); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + if (!loader.isLoaded()) { + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_HIT); + } else { + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_MISS); + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + } + return value; + } + + @Override + public void invalidate(K key) { + if (this.cache.containsKey(key)) { + eventDispatcher.dispatchRemovalEvent( + new StoreAwareCacheRemovalNotification<>(key, null, RemovalReason.INVALIDATED, CacheStoreType.ON_HEAP) + ); + } + this.cache.remove(key); + + } + + @Override + public V compute(K key, LoadAwareCacheLoader loader) throws Exception { + if (this.cache.size() >= maxSize) { // If it exceeds, just notify for evict. + eventDispatcher.dispatchRemovalEvent( + new StoreAwareCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP) + ); + return loader.load(key); + } + V value = this.cache.compute(key, ((k, v) -> { + try { + return loader.load(key); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + return value; + } + + @Override + public void invalidateAll() { + this.cache.clear(); + } + + @Override + public Iterable keys() { + return this.cache.keySet(); + } + + @Override + public long count() { + return this.cache.size(); + } + + @Override + public void refresh() { + + } + + @Override + public CacheStoreType getTierType() { + return CacheStoreType.ON_HEAP; + } + + public static class Builder extends StoreAwareCacheBuilder { + + int maxSize; + + @Override + public StoreAwareCache build() { + return new MockOnHeapCache<>(maxSize, this.getListenerConfiguration()); + } + + public Builder setMaxSize(int maxSize) { + this.maxSize = maxSize; + return this; + } + } +} diff --git a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java index 9de45835d3c2d..9666dc234f9a7 100644 --- a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java +++ b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java @@ -37,7 +37,7 @@ import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.routing.allocation.DiskThresholdSettings; import org.opensearch.common.cache.RemovalNotification; -import org.opensearch.common.cache.tier.enums.CacheStoreType; +import org.opensearch.common.cache.store.enums.CacheStoreType; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.OpenSearchExecutors; import org.opensearch.core.common.bytes.BytesArray; From 13a03a9bc64198355c9090e84a2545f47a0aa3da Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 7 Dec 2023 06:18:55 -0800 Subject: [PATCH 10/19] Fixing gradle missingDoc issue Signed-off-by: Sagar Upadhyaya --- .../cache/store/StoreAwareCacheRemovalNotification.java | 5 +++++ .../StoreAwareCacheEventListenerConfiguration.java | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java index 91c3b6c2c5e33..092779710b15f 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java +++ b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java @@ -12,6 +12,11 @@ import org.opensearch.common.cache.RemovalReason; import org.opensearch.common.cache.store.enums.CacheStoreType; +/** + * Removal notification for store aware cache. + * @param Type of key. + * @param Type of value. + */ public class StoreAwareCacheRemovalNotification extends RemovalNotification { private final CacheStoreType cacheStoreType; diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java index be49c82238ae8..1a449786b4b63 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java @@ -11,6 +11,11 @@ import java.util.EnumSet; import java.util.Objects; +/** + * Configuration class for event listener. Defines for which event types and listener, events needs to be sent. + * @param Type of key + * @param Type of value + */ public class StoreAwareCacheEventListenerConfiguration { private final EnumSet eventTypes; @@ -29,6 +34,9 @@ public StoreAwareCacheEventListener getEventListener() { return eventListener; } + /** + * Builder class + */ public static class Builder { private EnumSet eventTypes; private StoreAwareCacheEventListener eventListener; From 42a111d24bc0867b96617645ed0d5326b42efc50 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 21 Dec 2023 13:51:56 -0800 Subject: [PATCH 11/19] Changing listener logic, removing tiered cache integration with IRC Signed-off-by: Sagar Upadhyaya --- .../cache/{store/Cache.java => ICache.java} | 8 +- .../cache/store/OpenSearchOnHeapCache.java | 26 ++-- .../common/cache/store/StoreAwareCache.java | 5 +- .../StoreAwareCacheRemovalNotification.java | 2 + .../cache/store/StoreAwareCacheValue.java | 2 + .../builders/StoreAwareCacheBuilder.java | 16 +- .../cache/store/enums/CacheStoreType.java | 2 + .../cache/store/listeners/EventType.java | 2 + .../StoreAwareCacheEventListener.java | 2 + ...eAwareCacheEventListenerConfiguration.java | 60 -------- ...toreAwareCacheEventListenerDispatcher.java | 25 ---- ...areCacheListenerDispatcherDefaultImpl.java | 62 -------- .../listeners/dispatchers/package-info.java | 12 -- .../common/cache/tier/TieredCache.java | 6 +- .../cache/tier/TieredSpilloverCache.java | 104 ++++--------- .../cache/request/ShardRequestCache.java | 50 ++----- .../AbstractIndexShardCacheEntity.java | 25 +--- .../indices/IndicesRequestCache.java | 89 ++++------- .../cache/tier/TieredSpilloverCacheTests.java | 141 ++++++------------ .../indices/IndicesServiceCloseTests.java | 7 +- 20 files changed, 159 insertions(+), 487 deletions(-) rename server/src/main/java/org/opensearch/common/cache/{store/Cache.java => ICache.java} (82%) delete mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheEventListenerDispatcher.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheListenerDispatcherDefaultImpl.java delete mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/package-info.java diff --git a/server/src/main/java/org/opensearch/common/cache/store/Cache.java b/server/src/main/java/org/opensearch/common/cache/ICache.java similarity index 82% rename from server/src/main/java/org/opensearch/common/cache/store/Cache.java rename to server/src/main/java/org/opensearch/common/cache/ICache.java index 4b6167ddb2c7b..aa868cdf619ac 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/Cache.java +++ b/server/src/main/java/org/opensearch/common/cache/ICache.java @@ -6,16 +6,16 @@ * compatible open source license. */ -package org.opensearch.common.cache.store; - -import org.opensearch.common.cache.LoadAwareCacheLoader; +package org.opensearch.common.cache; /** * Represents a cache interface. * @param Type of key. * @param Type of value. + * + * @opensearch.experimental */ -public interface Cache { +public interface ICache { V get(K key); void put(K key, V value); diff --git a/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java b/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java index 067876119b6a9..408e2ef5bc70d 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java +++ b/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java @@ -15,20 +15,20 @@ import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; import org.opensearch.common.cache.store.enums.CacheStoreType; -import org.opensearch.common.cache.store.listeners.EventType; -import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheEventListenerDispatcher; -import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheListenerDispatcherDefaultImpl; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListener; /** * This variant of on-heap cache uses OpenSearch custom cache implementation. * @param Type of key. * @param Type of value. + * + * @opensearch.experimental */ public class OpenSearchOnHeapCache implements StoreAwareCache, RemovalListener { private final Cache cache; - private final StoreAwareCacheEventListenerDispatcher eventDispatcher; + private final StoreAwareCacheEventListener eventListener; public OpenSearchOnHeapCache(Builder builder) { CacheBuilder cacheBuilder = CacheBuilder.builder() @@ -39,16 +39,16 @@ public OpenSearchOnHeapCache(Builder builder) { cacheBuilder.setExpireAfterAccess(builder.getExpireAfterAcess()); } cache = cacheBuilder.build(); - this.eventDispatcher = new StoreAwareCacheListenerDispatcherDefaultImpl<>(builder.getListenerConfiguration()); + this.eventListener = builder.getEventListener(); } @Override public V get(K key) { V value = cache.get(key); if (value != null) { - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_HIT); + eventListener.onHit(key, value, CacheStoreType.ON_HEAP); } else { - eventDispatcher.dispatch(key, null, CacheStoreType.ON_HEAP, EventType.ON_MISS); + eventListener.onMiss(key, CacheStoreType.ON_HEAP); } return value; } @@ -56,17 +56,17 @@ public V get(K key) { @Override public void put(K key, V value) { cache.put(key, value); - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + eventListener.onCached(key, value, CacheStoreType.ON_HEAP); } @Override public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { V value = cache.computeIfAbsent(key, key1 -> loader.load(key)); if (!loader.isLoaded()) { - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_HIT); + eventListener.onHit(key, value, CacheStoreType.ON_HEAP); } else { - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_MISS); - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + eventListener.onMiss(key, CacheStoreType.ON_HEAP); + eventListener.onCached(key, value, CacheStoreType.ON_HEAP); } return value; } @@ -79,7 +79,7 @@ public void invalidate(K key) { @Override public V compute(K key, LoadAwareCacheLoader loader) throws Exception { V value = cache.compute(key, key1 -> loader.load(key)); - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + eventListener.onCached(key, value, CacheStoreType.ON_HEAP); return value; } @@ -110,7 +110,7 @@ public CacheStoreType getTierType() { @Override public void onRemoval(RemovalNotification notification) { - eventDispatcher.dispatchRemovalEvent( + eventListener.onRemoval( new StoreAwareCacheRemovalNotification<>( notification.getKey(), notification.getValue(), diff --git a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCache.java b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCache.java index f06802687e6ad..45ca48d94c140 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCache.java +++ b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCache.java @@ -8,13 +8,16 @@ package org.opensearch.common.cache.store; +import org.opensearch.common.cache.ICache; import org.opensearch.common.cache.store.enums.CacheStoreType; /** * Represents a cache with a specific type of store like onHeap, disk etc. * @param Type of key. * @param Type of value. + * + * @opensearch.experimental */ -public interface StoreAwareCache extends Cache { +public interface StoreAwareCache extends ICache { CacheStoreType getTierType(); } diff --git a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java index 092779710b15f..492dbff3532a1 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java +++ b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheRemovalNotification.java @@ -16,6 +16,8 @@ * Removal notification for store aware cache. * @param Type of key. * @param Type of value. + * + * @opensearch.internal */ public class StoreAwareCacheRemovalNotification extends RemovalNotification { private final CacheStoreType cacheStoreType; diff --git a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java index ac036f2f197d0..e911b1041ba31 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java +++ b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java @@ -13,6 +13,8 @@ /** * Represents a store aware cache value. * @param Type of value. + * + * @opensearch.internal */ public class StoreAwareCacheValue { private final V value; diff --git a/server/src/main/java/org/opensearch/common/cache/store/builders/StoreAwareCacheBuilder.java b/server/src/main/java/org/opensearch/common/cache/store/builders/StoreAwareCacheBuilder.java index 2da9fde277c4e..fc5aa48aae90f 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/builders/StoreAwareCacheBuilder.java +++ b/server/src/main/java/org/opensearch/common/cache/store/builders/StoreAwareCacheBuilder.java @@ -9,7 +9,7 @@ package org.opensearch.common.cache.store.builders; import org.opensearch.common.cache.store.StoreAwareCache; -import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; +import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListener; import org.opensearch.common.unit.TimeValue; import java.util.function.ToLongBiFunction; @@ -18,6 +18,8 @@ * Builder for store aware cache. * @param Type of key. * @param Type of value. + * + * @opensearch.internal */ public abstract class StoreAwareCacheBuilder { @@ -27,7 +29,7 @@ public abstract class StoreAwareCacheBuilder { private TimeValue expireAfterAcess; - private StoreAwareCacheEventListenerConfiguration listenerConfiguration; + private StoreAwareCacheEventListener eventListener; public StoreAwareCacheBuilder() {} @@ -46,10 +48,8 @@ public StoreAwareCacheBuilder setExpireAfterAccess(TimeValue expireAfterAc return this; } - public StoreAwareCacheBuilder setEventListenerConfiguration( - StoreAwareCacheEventListenerConfiguration listenerConfiguration - ) { - this.listenerConfiguration = listenerConfiguration; + public StoreAwareCacheBuilder setEventListener(StoreAwareCacheEventListener eventListener) { + this.eventListener = eventListener; return this; } @@ -65,8 +65,8 @@ public ToLongBiFunction getWeigher() { return weigher; } - public StoreAwareCacheEventListenerConfiguration getListenerConfiguration() { - return listenerConfiguration; + public StoreAwareCacheEventListener getEventListener() { + return eventListener; } public abstract StoreAwareCache build(); diff --git a/server/src/main/java/org/opensearch/common/cache/store/enums/CacheStoreType.java b/server/src/main/java/org/opensearch/common/cache/store/enums/CacheStoreType.java index 9d7d2b57af743..04c0825787b66 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/enums/CacheStoreType.java +++ b/server/src/main/java/org/opensearch/common/cache/store/enums/CacheStoreType.java @@ -10,6 +10,8 @@ /** * Cache store types in tiered cache. + * + * @opensearch.internal */ public enum CacheStoreType { diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java index ceecba787ef55..f81b94ec29858 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java @@ -10,6 +10,8 @@ /** * Describes various event types which is used to notify the called via listener. + * + * @opensearch.internal */ public enum EventType { diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListener.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListener.java index 76d669b50c696..6d7e4b39aaf9f 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListener.java +++ b/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListener.java @@ -15,6 +15,8 @@ * This can be used to listen to tiered caching events * @param Type of key * @param Type of value + * + * @opensearch.internal */ public interface StoreAwareCacheEventListener { diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java deleted file mode 100644 index 1a449786b4b63..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/store/listeners/StoreAwareCacheEventListenerConfiguration.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.store.listeners; - -import java.util.EnumSet; -import java.util.Objects; - -/** - * Configuration class for event listener. Defines for which event types and listener, events needs to be sent. - * @param Type of key - * @param Type of value - */ -public class StoreAwareCacheEventListenerConfiguration { - - private final EnumSet eventTypes; - private final StoreAwareCacheEventListener eventListener; - - public StoreAwareCacheEventListenerConfiguration(Builder builder) { - this.eventListener = Objects.requireNonNull(builder.eventListener); - this.eventTypes = Objects.requireNonNull(builder.eventTypes); - } - - public EnumSet getEventTypes() { - return eventTypes; - } - - public StoreAwareCacheEventListener getEventListener() { - return eventListener; - } - - /** - * Builder class - */ - public static class Builder { - private EnumSet eventTypes; - private StoreAwareCacheEventListener eventListener; - - public Builder() {} - - public Builder setEventTypes(EnumSet eventTypes) { - this.eventTypes = eventTypes; - return this; - } - - public Builder setEventListener(StoreAwareCacheEventListener eventListener) { - this.eventListener = eventListener; - return this; - } - - public StoreAwareCacheEventListenerConfiguration build() { - return new StoreAwareCacheEventListenerConfiguration<>(this); - } - } -} diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheEventListenerDispatcher.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheEventListenerDispatcher.java deleted file mode 100644 index e67edbd602a56..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheEventListenerDispatcher.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.store.listeners.dispatchers; - -import org.opensearch.common.Nullable; -import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; -import org.opensearch.common.cache.store.enums.CacheStoreType; -import org.opensearch.common.cache.store.listeners.EventType; - -/** - * Encapsulates all logic to dispatch events for specific cache store type. - * @param Type of key. - * @param Type of value. - */ -public interface StoreAwareCacheEventListenerDispatcher { - void dispatch(K key, @Nullable V value, CacheStoreType cacheStoreType, EventType eventType); - - void dispatchRemovalEvent(StoreAwareCacheRemovalNotification notification); -} diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheListenerDispatcherDefaultImpl.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheListenerDispatcherDefaultImpl.java deleted file mode 100644 index ae4baf8565528..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/StoreAwareCacheListenerDispatcherDefaultImpl.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.store.listeners.dispatchers; - -import org.opensearch.common.Nullable; -import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; -import org.opensearch.common.cache.store.enums.CacheStoreType; -import org.opensearch.common.cache.store.listeners.EventType; -import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; - -/** - * Default implementation of event listener dispatcher. - * @param Type of key. - * @param Type of value. - */ -public class StoreAwareCacheListenerDispatcherDefaultImpl implements StoreAwareCacheEventListenerDispatcher { - - private final StoreAwareCacheEventListenerConfiguration listenerConfiguration; - - public StoreAwareCacheListenerDispatcherDefaultImpl(StoreAwareCacheEventListenerConfiguration listenerConfiguration) { - this.listenerConfiguration = listenerConfiguration; - } - - @Override - public void dispatch(K key, @Nullable V value, CacheStoreType cacheStoreType, EventType eventType) { - switch (eventType) { - case ON_CACHED: - if (this.listenerConfiguration.getEventTypes().contains(EventType.ON_CACHED)) { - this.listenerConfiguration.getEventListener().onCached(key, value, cacheStoreType); - } - break; - case ON_HIT: - if (this.listenerConfiguration.getEventTypes().contains(EventType.ON_HIT)) { - this.listenerConfiguration.getEventListener().onHit(key, value, cacheStoreType); - } - break; - case ON_MISS: - if (this.listenerConfiguration.getEventTypes().contains(EventType.ON_MISS)) { - this.listenerConfiguration.getEventListener().onMiss(key, cacheStoreType); - } - break; - case ON_REMOVAL: - // Handled separately - break; - default: - throw new IllegalArgumentException("Unsupported event type: " + eventType); - } - } - - @Override - public void dispatchRemovalEvent(StoreAwareCacheRemovalNotification notification) { - if (this.listenerConfiguration.getEventTypes().contains(EventType.ON_REMOVAL)) { - this.listenerConfiguration.getEventListener().onRemoval(notification); - } - } -} diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/package-info.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/package-info.java deleted file mode 100644 index a155bbba82bfc..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/store/listeners/dispatchers/package-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/** - * Base package for listener dispatchers. - */ -package org.opensearch.common.cache.store.listeners.dispatchers; diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java index 94ddb32acd201..f99d1adb2ab7e 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java @@ -8,15 +8,17 @@ package org.opensearch.common.cache.tier; -import org.opensearch.common.cache.store.Cache; +import org.opensearch.common.cache.ICache; import org.opensearch.common.cache.store.enums.CacheStoreType; /** * This represents a cache comprising of multiple tiers/layers. * @param Type of key * @param Type of value + * + * @opensearch.experimental */ -public interface TieredCache extends Cache { +public interface TieredCache extends ICache { Iterable cacheKeys(CacheStoreType type); void refresh(CacheStoreType type); diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java index db340a9205b20..cd572f1706332 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java @@ -8,67 +8,57 @@ package org.opensearch.common.cache.tier; +import org.opensearch.common.cache.ICache; import org.opensearch.common.cache.LoadAwareCacheLoader; import org.opensearch.common.cache.RemovalReason; -import org.opensearch.common.cache.store.Cache; import org.opensearch.common.cache.store.StoreAwareCache; import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; import org.opensearch.common.cache.store.StoreAwareCacheValue; import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; import org.opensearch.common.cache.store.enums.CacheStoreType; -import org.opensearch.common.cache.store.listeners.EventType; import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListener; -import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; -import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheListenerDispatcherDefaultImpl; +import org.opensearch.common.util.iterable.Iterables; import java.util.Arrays; import java.util.Collections; -import java.util.EnumSet; -import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.function.Function; /** - * This cache spillover the evicted items from upper tier to lower tier. For now, we are spilling the in-memory - * cache items to disk tier cache. All the new items are cached onHeap and if any items are evicted are moved to disk - * cache. + * This cache spillover the evicted items from heap tier to disk tier. All the new items are first cached on heap + * and the items evicted from on heap cache are moved to disk based cache. If disk based cache also gets full, + * then items are eventually evicted from it and removed which will result in cache miss. + * * @param Type of key * @param Type of value + * + * @opensearch.experimental */ public class TieredSpilloverCache implements TieredCache, StoreAwareCacheEventListener { private final Optional> onDiskCache; private final StoreAwareCache onHeapCache; - private final StoreAwareCacheListenerDispatcherDefaultImpl eventDispatcher; + private final StoreAwareCacheEventListener listener; /** - * Maintains caching tiers in order of get calls. + * Maintains caching tiers in ascending order of cache latency. */ private final List> cacheList; TieredSpilloverCache(Builder builder) { Objects.requireNonNull(builder.onHeapCacheBuilder, "onHeap cache builder can't be null"); - this.onHeapCache = builder.onHeapCacheBuilder.setEventListenerConfiguration(getCacheEventListenerConfiguration()).build(); + this.onHeapCache = builder.onHeapCacheBuilder.setEventListener(this).build(); if (builder.onDiskCacheBuilder != null) { - this.onDiskCache = Optional.of( - builder.onDiskCacheBuilder.setEventListenerConfiguration(getCacheEventListenerConfiguration()).build() - ); + this.onDiskCache = Optional.of(builder.onDiskCacheBuilder.setEventListener(this).build()); } else { this.onDiskCache = Optional.empty(); } - this.eventDispatcher = new StoreAwareCacheListenerDispatcherDefaultImpl(builder.listenerConfiguration); + this.listener = builder.listener; this.cacheList = this.onDiskCache.map(diskTier -> Arrays.asList(this.onHeapCache, diskTier)).orElse(List.of(this.onHeapCache)); } - private StoreAwareCacheEventListenerConfiguration getCacheEventListenerConfiguration() { - return new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(this) - .setEventTypes(EnumSet.of(EventType.ON_REMOVAL, EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS)) - .build(); - } - @Override public V get(K key) { StoreAwareCacheValue cacheValue = getValueFromTieredCache().apply(key); @@ -97,8 +87,8 @@ public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Except @Override public void invalidate(K key) { // We are trying to invalidate the key from all caches though it would be present in only of them. - // Doing this as we don't where it is located. We could do a get from both and check that, but what will also - // trigger a hit/miss listener event, so ignoring it for now. + // Doing this as we don't know where it is located. We could do a get from both and check that, but what will + // also trigger a hit/miss listener event, so ignoring it for now. for (StoreAwareCache storeAwareCache : cacheList) { storeAwareCache.invalidate(key); } @@ -124,7 +114,7 @@ public Iterable keys() { } else { onDiskKeysIterable = Collections::emptyIterator; } - return new MergedIterable<>(onHeapCache.keys(), onDiskKeysIterable); + return Iterables.concat(onHeapCache.keys(), onDiskKeysIterable); } @Override @@ -166,7 +156,7 @@ public void refresh(CacheStoreType type) { onHeapCache.refresh(); break; case DISK: - onDiskCache.ifPresent(Cache::refresh); + onDiskCache.ifPresent(ICache::refresh); break; default: throw new IllegalArgumentException("Unsupported Cache store type: " + type); @@ -175,12 +165,13 @@ public void refresh(CacheStoreType type) { @Override public void onMiss(K key, CacheStoreType cacheStoreType) { - eventDispatcher.dispatch(key, null, cacheStoreType, EventType.ON_MISS); + listener.onMiss(key, cacheStoreType); } @Override public void onRemoval(StoreAwareCacheRemovalNotification notification) { - if (RemovalReason.EVICTED.equals(notification.getRemovalReason())) { + if (RemovalReason.EVICTED.equals(notification.getRemovalReason()) + || RemovalReason.CAPACITY.equals(notification.getRemovalReason())) { switch (notification.getCacheStoreType()) { case ON_HEAP: onDiskCache.ifPresent(diskTier -> { diskTier.put(notification.getKey(), notification.getValue()); }); @@ -189,17 +180,17 @@ public void onRemoval(StoreAwareCacheRemovalNotification notification) { break; } } - eventDispatcher.dispatchRemovalEvent(notification); + listener.onRemoval(notification); } @Override public void onHit(K key, V value, CacheStoreType cacheStoreType) { - eventDispatcher.dispatch(key, value, cacheStoreType, EventType.ON_HIT); + listener.onHit(key, value, cacheStoreType); } @Override public void onCached(K key, V value, CacheStoreType cacheStoreType) { - eventDispatcher.dispatch(key, value, cacheStoreType, EventType.ON_CACHED); + listener.onCached(key, value, cacheStoreType); } private Function> getValueFromTieredCache() { @@ -222,7 +213,7 @@ private Function> getValueFromTieredCache() { public static class Builder { private StoreAwareCacheBuilder onHeapCacheBuilder; private StoreAwareCacheBuilder onDiskCacheBuilder; - private StoreAwareCacheEventListenerConfiguration listenerConfiguration; + private StoreAwareCacheEventListener listener; public Builder() {} @@ -236,8 +227,8 @@ public Builder setOnDiskCacheBuilder(StoreAwareCacheBuilder onDiskCa return this; } - public Builder setListenerConfiguration(StoreAwareCacheEventListenerConfiguration listenerConfiguration) { - this.listenerConfiguration = listenerConfiguration; + public Builder setListener(StoreAwareCacheEventListener listener) { + this.listener = listener; return this; } @@ -245,47 +236,4 @@ public TieredSpilloverCache build() { return new TieredSpilloverCache<>(this); } } - - /** - * Returns a merged iterable which can be used to iterate over both onHeap and disk cache keys. - * @param Type of key. - */ - public class MergedIterable implements Iterable { - - private final Iterable onHeapKeysIterable; - private final Iterable onDiskKeysIterable; - - public MergedIterable(Iterable onHeapKeysIterable, Iterable onDiskKeysIterable) { - this.onHeapKeysIterable = onHeapKeysIterable; - this.onDiskKeysIterable = onDiskKeysIterable; - } - - @Override - public Iterator iterator() { - return new Iterator() { - private final Iterator onHeapIterator = onHeapKeysIterable.iterator(); - private final Iterator onDiskIterator = onDiskKeysIterable.iterator(); - private boolean useOnHeapIterator = true; - - @Override - public boolean hasNext() { - return onHeapIterator.hasNext() || onDiskIterator.hasNext(); - } - - @Override - public K next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - - if (useOnHeapIterator && onHeapIterator.hasNext()) { - return onHeapIterator.next(); - } else { - useOnHeapIterator = false; - return onDiskIterator.next(); - } - } - }; - } - } } diff --git a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java index 60d17bbebf8ce..bb35a09ccab46 100644 --- a/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java +++ b/server/src/main/java/org/opensearch/index/cache/request/ShardRequestCache.java @@ -34,12 +34,9 @@ import org.apache.lucene.util.Accountable; import org.opensearch.common.annotation.PublicApi; -import org.opensearch.common.cache.store.enums.CacheStoreType; import org.opensearch.common.metrics.CounterMetric; import org.opensearch.core.common.bytes.BytesReference; -import java.util.EnumMap; - /** * Tracks the portion of the request cache in use for a particular shard. * @@ -48,43 +45,30 @@ @PublicApi(since = "1.0.0") public final class ShardRequestCache { - private EnumMap statsHolder = new EnumMap<>(CacheStoreType.class); - - public ShardRequestCache() { - for (CacheStoreType cacheStoreType : CacheStoreType.values()) { - statsHolder.put(cacheStoreType, new StatsHolder()); - } - } + final CounterMetric evictionsMetric = new CounterMetric(); + final CounterMetric totalMetric = new CounterMetric(); + final CounterMetric hitCount = new CounterMetric(); + final CounterMetric missCount = new CounterMetric(); public RequestCacheStats stats() { - // TODO: Change RequestCacheStats to support disk tier stats. - return new RequestCacheStats( - statsHolder.get(CacheStoreType.ON_HEAP).totalMetric.count(), - statsHolder.get(CacheStoreType.ON_HEAP).evictionsMetric.count(), - statsHolder.get(CacheStoreType.ON_HEAP).hitCount.count(), - statsHolder.get(CacheStoreType.ON_HEAP).missCount.count() - ); + return new RequestCacheStats(totalMetric.count(), evictionsMetric.count(), hitCount.count(), missCount.count()); } - public void onHit(CacheStoreType cacheStoreType) { - statsHolder.get(cacheStoreType).hitCount.inc(); + public void onHit() { + hitCount.inc(); } - public void onMiss(CacheStoreType cacheStoreType) { - statsHolder.get(cacheStoreType).missCount.inc(); + public void onMiss() { + missCount.inc(); } - public void onCached(Accountable key, BytesReference value, CacheStoreType cacheStoreType) { - statsHolder.get(cacheStoreType).totalMetric.inc(key.ramBytesUsed() + value.ramBytesUsed()); + public void onCached(Accountable key, BytesReference value) { + totalMetric.inc(key.ramBytesUsed() + value.ramBytesUsed()); } public void onRemoval(Accountable key, BytesReference value, boolean evicted) { - onRemoval(key, value, evicted, CacheStoreType.ON_HEAP); // By default On heap cache. - } - - public void onRemoval(Accountable key, BytesReference value, boolean evicted, CacheStoreType cacheStoreType) { if (evicted) { - statsHolder.get(cacheStoreType).evictionsMetric.inc(); + evictionsMetric.inc(); } long dec = 0; if (key != null) { @@ -93,14 +77,6 @@ public void onRemoval(Accountable key, BytesReference value, boolean evicted, Ca if (value != null) { dec += value.ramBytesUsed(); } - statsHolder.get(cacheStoreType).totalMetric.dec(dec); - } - - static class StatsHolder { - - final CounterMetric evictionsMetric = new CounterMetric(); - final CounterMetric totalMetric = new CounterMetric(); - final CounterMetric hitCount = new CounterMetric(); - final CounterMetric missCount = new CounterMetric(); + totalMetric.dec(dec); } } diff --git a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java index 8b77cced0e27d..bb1201cb910a9 100644 --- a/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java +++ b/server/src/main/java/org/opensearch/indices/AbstractIndexShardCacheEntity.java @@ -34,8 +34,6 @@ import org.opensearch.common.cache.RemovalNotification; import org.opensearch.common.cache.RemovalReason; -import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; -import org.opensearch.common.cache.store.enums.CacheStoreType; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.index.cache.request.ShardRequestCache; import org.opensearch.index.shard.IndexShard; @@ -53,31 +51,22 @@ abstract class AbstractIndexShardCacheEntity implements IndicesRequestCache.Cach protected abstract ShardRequestCache stats(); @Override - public final void onCached(IndicesRequestCache.Key key, BytesReference value, CacheStoreType cacheStoreType) { - stats().onCached(key, value, cacheStoreType); + public final void onCached(IndicesRequestCache.Key key, BytesReference value) { + stats().onCached(key, value); } @Override - public final void onHit(CacheStoreType cacheStoreType) { - stats().onHit(cacheStoreType); + public final void onHit() { + stats().onHit(); } @Override - public final void onMiss(CacheStoreType cacheStoreType) { - stats().onMiss(cacheStoreType); + public final void onMiss() { + stats().onMiss(); } @Override public final void onRemoval(RemovalNotification notification) { - if (notification instanceof StoreAwareCacheRemovalNotification) { - stats().onRemoval( - notification.getKey(), - notification.getValue(), - notification.getRemovalReason() == RemovalReason.EVICTED, - ((StoreAwareCacheRemovalNotification) notification).getCacheStoreType() - ); - } else { - stats().onRemoval(notification.getKey(), notification.getValue(), notification.getRemovalReason() == RemovalReason.EVICTED); - } + stats().onRemoval(notification.getKey(), notification.getValue(), notification.getRemovalReason() == RemovalReason.EVICTED); } } diff --git a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java index df935ac6e4f8c..4a19f8eb8714d 100644 --- a/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java +++ b/server/src/main/java/org/opensearch/indices/IndicesRequestCache.java @@ -39,18 +39,11 @@ import org.apache.lucene.util.Accountable; import org.apache.lucene.util.RamUsageEstimator; import org.opensearch.common.CheckedSupplier; -import org.opensearch.common.cache.LoadAwareCacheLoader; +import org.opensearch.common.cache.Cache; +import org.opensearch.common.cache.CacheBuilder; +import org.opensearch.common.cache.CacheLoader; +import org.opensearch.common.cache.RemovalListener; import org.opensearch.common.cache.RemovalNotification; -import org.opensearch.common.cache.store.Cache; -import org.opensearch.common.cache.store.OpenSearchOnHeapCache; -import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; -import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; -import org.opensearch.common.cache.store.enums.CacheStoreType; -import org.opensearch.common.cache.store.listeners.EventType; -import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListener; -import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; -import org.opensearch.common.cache.tier.TieredCache; -import org.opensearch.common.cache.tier.TieredSpilloverCache; import org.opensearch.common.lucene.index.OpenSearchDirectoryReader; import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Setting.Property; @@ -64,7 +57,6 @@ import java.io.IOException; import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import java.util.Objects; @@ -86,7 +78,7 @@ * * @opensearch.internal */ -public final class IndicesRequestCache implements StoreAwareCacheEventListener, Closeable { +public final class IndicesRequestCache implements RemovalListener, Closeable { private static final Logger logger = LogManager.getLogger(IndicesRequestCache.class); @@ -121,18 +113,14 @@ public final class IndicesRequestCache implements StoreAwareCacheEventListener openSearchOnHeapCacheBuilder = new OpenSearchOnHeapCache.Builder() - .setWeigher((k, v) -> k.ramBytesUsed() + v.ramBytesUsed()) - .setMaximumWeightInBytes(sizeInBytes) - .setExpireAfterAccess(expire); - - cache = new TieredSpilloverCache.Builder().setOnHeapCacheBuilder(openSearchOnHeapCacheBuilder) - .setListenerConfiguration( - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(this) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build() - ) - .build(); + CacheBuilder cacheBuilder = CacheBuilder.builder() + .setMaximumWeight(sizeInBytes) + .weigher((k, v) -> k.ramBytesUsed() + v.ramBytesUsed()) + .removalListener(this); + if (expire != null) { + cacheBuilder.setExpireAfterAccess(expire); + } + cache = cacheBuilder.build(); } @Override @@ -146,25 +134,10 @@ void clear(CacheEntity entity) { } @Override - public void onMiss(Key key, CacheStoreType cacheStoreType) { - key.entity.onMiss(cacheStoreType); - } - - @Override - public void onRemoval(StoreAwareCacheRemovalNotification notification) { + public void onRemoval(RemovalNotification notification) { notification.getKey().entity.onRemoval(notification); } - @Override - public void onHit(Key key, BytesReference value, CacheStoreType cacheStoreType) { - key.entity.onHit(cacheStoreType); - } - - @Override - public void onCached(Key key, BytesReference value, CacheStoreType cacheStoreType) { - key.entity.onCached(key, value, cacheStoreType); - } - BytesReference getOrCompute( CacheEntity cacheEntity, CheckedSupplier loader, @@ -176,6 +149,7 @@ BytesReference getOrCompute( Loader cacheLoader = new Loader(cacheEntity, loader); BytesReference value = cache.computeIfAbsent(key, cacheLoader); if (cacheLoader.isLoaded()) { + key.entity.onMiss(); // see if its the first time we see this reader, and make sure to register a cleanup key CleanupKey cleanupKey = new CleanupKey(cacheEntity, reader.getReaderCacheHelper().getKey()); if (!registeredClosedListeners.containsKey(cleanupKey)) { @@ -184,6 +158,8 @@ BytesReference getOrCompute( OpenSearchDirectoryReader.addReaderCloseListener(reader, cleanupKey); } } + } else { + key.entity.onHit(); } return value; } @@ -204,7 +180,7 @@ void invalidate(CacheEntity cacheEntity, DirectoryReader reader, BytesReference * * @opensearch.internal */ - private static class Loader implements LoadAwareCacheLoader { + private static class Loader implements CacheLoader { private final CacheEntity entity; private final CheckedSupplier loader; @@ -222,6 +198,7 @@ public boolean isLoaded() { @Override public BytesReference load(Key key) throws Exception { BytesReference value = loader.get(); + entity.onCached(key, value); loaded = true; return value; } @@ -235,7 +212,7 @@ interface CacheEntity extends Accountable { /** * Called after the value was loaded. */ - void onCached(Key key, BytesReference value, CacheStoreType cacheStoreType); + void onCached(Key key, BytesReference value); /** * Returns true iff the resource behind this entity is still open ie. @@ -252,12 +229,12 @@ interface CacheEntity extends Accountable { /** * Called each time this entity has a cache hit. */ - void onHit(CacheStoreType cacheStoreType); + void onHit(); /** * Called each time this entity has a cache miss. */ - void onMiss(CacheStoreType cacheStoreType); + void onMiss(); /** * Called when this entity instance is removed @@ -370,7 +347,7 @@ synchronized void cleanCache() { } } if (!currentKeysToClean.isEmpty() || !currentFullClean.isEmpty()) { - for (Iterator iterator = getIterable(CacheStoreType.ON_HEAP).iterator(); iterator.hasNext();) { + for (Iterator iterator = cache.keys().iterator(); iterator.hasNext();) { Key key = iterator.next(); if (currentFullClean.contains(key.entity.getCacheIdentity())) { iterator.remove(); @@ -381,29 +358,13 @@ synchronized void cleanCache() { } } } - refresh(CacheStoreType.ON_HEAP); - } - - private Iterable getIterable(CacheStoreType cacheStoreType) { - if (cache instanceof TieredCache) { - return ((TieredCache) cache).cacheKeys(cacheStoreType); - } else { - return cache.keys(); - } - } - - private void refresh(CacheStoreType cacheStoreType) { - if (cache instanceof TieredCache) { - ((TieredCache) cache).refresh(cacheStoreType); - } else { - cache.refresh(); - } + cache.refresh(); } /** * Returns the current size of the cache */ - long count() { + int count() { return cache.count(); } diff --git a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java index 152b74732ccb1..c43de6cf50b89 100644 --- a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java +++ b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java @@ -14,17 +14,12 @@ import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; import org.opensearch.common.cache.store.enums.CacheStoreType; -import org.opensearch.common.cache.store.listeners.EventType; import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListener; -import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListenerConfiguration; -import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheEventListenerDispatcher; -import org.opensearch.common.cache.store.listeners.dispatchers.StoreAwareCacheListenerDispatcherDefaultImpl; import org.opensearch.common.metrics.CounterMetric; import org.opensearch.test.OpenSearchTestCase; import java.util.ArrayList; import java.util.EnumMap; -import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.UUID; @@ -35,14 +30,10 @@ public class TieredSpilloverCacheTests extends OpenSearchTestCase { public void testComputeIfAbsentWithoutAnyOnHeapCacheEviction() throws Exception { int onHeapCacheSize = randomIntBetween(10, 30); MockCacheEventListener eventListener = new MockCacheEventListener(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, randomIntBetween(1, 4), - eventListenerConfiguration + eventListener ); int numOfItems1 = randomIntBetween(1, onHeapCacheSize / 2 - 1); List keys = new ArrayList<>(); @@ -83,14 +74,10 @@ public void testComputeIfAbsentWithEvictionsFromOnHeapCache() throws Exception { int diskCacheSize = randomIntBetween(60, 100); int totalSize = onHeapCacheSize + diskCacheSize; MockCacheEventListener eventListener = new MockCacheEventListener(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListenerConfiguration + eventListener ); // Put values in cache more than it's size and cause evictions from onHeap. @@ -154,14 +141,10 @@ public void testComputeIfAbsentWithEvictionsFromBothTier() throws Exception { int totalSize = onHeapCacheSize + diskCacheSize; MockCacheEventListener eventListener = new MockCacheEventListener(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListenerConfiguration + eventListener ); int numOfItems = randomIntBetween(totalSize + 1, totalSize * 3); @@ -179,14 +162,10 @@ public void testGetAndCount() throws Exception { int totalSize = onHeapCacheSize + diskCacheSize; MockCacheEventListener eventListener = new MockCacheEventListener(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListenerConfiguration + eventListener ); int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); @@ -223,17 +202,13 @@ public void testGetAndCount() throws Exception { public void testWithDiskTierNull() throws Exception { int onHeapCacheSize = randomIntBetween(10, 30); MockCacheEventListener eventListener = new MockCacheEventListener(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); StoreAwareCacheBuilder onHeapCacheBuilder = new MockOnHeapCache.Builder().setMaxSize( onHeapCacheSize ); TieredSpilloverCache tieredSpilloverCache = new TieredSpilloverCache.Builder() .setOnHeapCacheBuilder(onHeapCacheBuilder) - .setListenerConfiguration(eventListenerConfiguration) + .setListener(eventListener) .build(); int numOfItems = randomIntBetween(onHeapCacheSize + 1, onHeapCacheSize * 3); @@ -252,14 +227,10 @@ public void testPut() { int diskCacheSize = randomIntBetween(onHeapCacheSize + 1, 100); MockCacheEventListener eventListener = new MockCacheEventListener<>(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListenerConfiguration + eventListener ); String key = UUID.randomUUID().toString(); String value = UUID.randomUUID().toString(); @@ -273,14 +244,10 @@ public void testInvalidate() { int diskCacheSize = 10; MockCacheEventListener eventListener = new MockCacheEventListener<>(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListenerConfiguration + eventListener ); String key = UUID.randomUUID().toString(); String value = UUID.randomUUID().toString(); @@ -308,14 +275,10 @@ public void testInvalidate() { public void testComputeWithoutAnyOnHeapEvictions() throws Exception { int onHeapCacheSize = randomIntBetween(10, 30); MockCacheEventListener eventListener = new MockCacheEventListener(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache spilloverStrategyService = intializeTieredSpilloverCache( onHeapCacheSize, randomIntBetween(1, 4), - eventListenerConfiguration + eventListener ); int numOfItems1 = randomIntBetween(1, onHeapCacheSize / 2 - 1); List keys = new ArrayList<>(); @@ -335,14 +298,10 @@ public void testCacheKeys() throws Exception { int totalSize = onHeapCacheSize + diskCacheSize; MockCacheEventListener eventListener = new MockCacheEventListener<>(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListenerConfiguration + eventListener ); // Put values in cache more than it's size and cause evictions from onHeap. int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); @@ -392,14 +351,10 @@ public void testRefresh() { int diskCacheSize = randomIntBetween(60, 100); MockCacheEventListener eventListener = new MockCacheEventListener<>(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListenerConfiguration + eventListener ); tieredSpilloverCache.refresh(CacheStoreType.ON_HEAP); tieredSpilloverCache.refresh(CacheStoreType.DISK); @@ -412,14 +367,10 @@ public void testInvalidateAll() throws Exception { int totalSize = onHeapCacheSize + diskCacheSize; MockCacheEventListener eventListener = new MockCacheEventListener<>(); - StoreAwareCacheEventListenerConfiguration eventListenerConfiguration = - new StoreAwareCacheEventListenerConfiguration.Builder().setEventListener(eventListener) - .setEventTypes(EnumSet.of(EventType.ON_CACHED, EventType.ON_HIT, EventType.ON_MISS, EventType.ON_REMOVAL)) - .build(); TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListenerConfiguration + eventListener ); // Put values in cache more than it's size and cause evictions from onHeap. int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); @@ -504,7 +455,7 @@ public boolean isLoaded() { private TieredSpilloverCache intializeTieredSpilloverCache( int onHeapCacheSize, int diksCacheSize, - StoreAwareCacheEventListenerConfiguration listenerConfiguration + StoreAwareCacheEventListener eventListener ) { StoreAwareCacheBuilder diskCacheBuilder = new MockOnDiskCache.Builder().setMaxSize(diksCacheSize); StoreAwareCacheBuilder onHeapCacheBuilder = new MockOnHeapCache.Builder().setMaxSize( @@ -512,7 +463,7 @@ private TieredSpilloverCache intializeTieredSpilloverCache( ); return new TieredSpilloverCache.Builder().setOnHeapCacheBuilder(onHeapCacheBuilder) .setOnDiskCacheBuilder(diskCacheBuilder) - .setListenerConfiguration(listenerConfiguration) + .setListener(eventListener) .build(); } } @@ -521,11 +472,11 @@ class MockOnDiskCache implements StoreAwareCache { Map cache; int maxSize; - StoreAwareCacheEventListenerDispatcher eventDispatcher; + StoreAwareCacheEventListener eventListener; - MockOnDiskCache(int maxSize, StoreAwareCacheEventListenerConfiguration eventListenerConfiguration) { + MockOnDiskCache(int maxSize, StoreAwareCacheEventListener eventListener) { this.maxSize = maxSize; - this.eventDispatcher = new StoreAwareCacheListenerDispatcherDefaultImpl<>(eventListenerConfiguration); + this.eventListener = eventListener; this.cache = new ConcurrentHashMap(); } @@ -533,9 +484,9 @@ class MockOnDiskCache implements StoreAwareCache { public V get(K key) { V value = cache.get(key); if (value != null) { - eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_HIT); + eventListener.onHit(key, value, CacheStoreType.DISK); } else { - eventDispatcher.dispatch(key, null, CacheStoreType.DISK, EventType.ON_MISS); + eventListener.onMiss(key, CacheStoreType.DISK); } return value; } @@ -543,13 +494,11 @@ public V get(K key) { @Override public void put(K key, V value) { if (this.cache.size() >= maxSize) { // For simplification - eventDispatcher.dispatchRemovalEvent( - new StoreAwareCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.DISK) - ); + eventListener.onRemoval(new StoreAwareCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.DISK)); return; } this.cache.put(key, value); - eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_CACHED); + eventListener.onCached(key, value, CacheStoreType.DISK); } @Override @@ -562,10 +511,10 @@ public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Except } }); if (!loader.isLoaded()) { - eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_HIT); + eventListener.onHit(key, value, CacheStoreType.DISK); } else { - eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_MISS); - eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_CACHED); + eventListener.onMiss(key, CacheStoreType.DISK); + eventListener.onCached(key, value, CacheStoreType.DISK); } return value; } @@ -573,9 +522,7 @@ public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Except @Override public void invalidate(K key) { if (this.cache.containsKey(key)) { - eventDispatcher.dispatchRemovalEvent( - new StoreAwareCacheRemovalNotification<>(key, null, RemovalReason.INVALIDATED, CacheStoreType.DISK) - ); + eventListener.onRemoval(new StoreAwareCacheRemovalNotification<>(key, null, RemovalReason.INVALIDATED, CacheStoreType.DISK)); } this.cache.remove(key); } @@ -583,7 +530,7 @@ public void invalidate(K key) { @Override public V compute(K key, LoadAwareCacheLoader loader) throws Exception { if (this.cache.size() >= maxSize) { // If it exceeds, just notify for evict. - eventDispatcher.dispatchRemovalEvent( + eventListener.onRemoval( new StoreAwareCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.DISK) ); return loader.load(key); @@ -595,7 +542,7 @@ public V compute(K key, LoadAwareCacheLoader loader) throws Exception { throw new RuntimeException(e); } })); - eventDispatcher.dispatch(key, value, CacheStoreType.DISK, EventType.ON_CACHED); + eventListener.onCached(key, value, CacheStoreType.DISK); return value; } @@ -628,7 +575,7 @@ public static class Builder extends StoreAwareCacheBuilder { @Override public StoreAwareCache build() { - return new MockOnDiskCache(maxSize, this.getListenerConfiguration()); + return new MockOnDiskCache(maxSize, this.getEventListener()); } public Builder setMaxSize(int maxSize) { @@ -642,21 +589,21 @@ class MockOnHeapCache implements StoreAwareCache { Map cache; int maxSize; - StoreAwareCacheEventListenerDispatcher eventDispatcher; + StoreAwareCacheEventListener eventListener; - MockOnHeapCache(int size, StoreAwareCacheEventListenerConfiguration eventListenerConfiguration) { + MockOnHeapCache(int size, StoreAwareCacheEventListener eventListener) { maxSize = size; this.cache = new ConcurrentHashMap(); - this.eventDispatcher = new StoreAwareCacheListenerDispatcherDefaultImpl<>(eventListenerConfiguration); + this.eventListener = eventListener; } @Override public V get(K key) { V value = cache.get(key); if (value != null) { - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_HIT); + eventListener.onHit(key, value, CacheStoreType.ON_HEAP); } else { - eventDispatcher.dispatch(key, null, CacheStoreType.ON_HEAP, EventType.ON_MISS); + eventListener.onMiss(key, CacheStoreType.ON_HEAP); } return value; } @@ -664,19 +611,17 @@ public V get(K key) { @Override public void put(K key, V value) { if (this.cache.size() >= maxSize) { - eventDispatcher.dispatchRemovalEvent( - new StoreAwareCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.ON_HEAP) - ); + eventListener.onRemoval(new StoreAwareCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.ON_HEAP)); return; } this.cache.put(key, value); - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + eventListener.onCached(key, value, CacheStoreType.ON_HEAP); } @Override public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { if (this.cache.size() >= maxSize) { // If it exceeds, just notify for evict. - eventDispatcher.dispatchRemovalEvent( + eventListener.onRemoval( new StoreAwareCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP) ); return loader.load(key); @@ -689,10 +634,10 @@ public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Except } }); if (!loader.isLoaded()) { - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_HIT); + eventListener.onHit(key, value, CacheStoreType.ON_HEAP); } else { - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_MISS); - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + eventListener.onMiss(key, CacheStoreType.ON_HEAP); + eventListener.onCached(key, value, CacheStoreType.ON_HEAP); } return value; } @@ -700,9 +645,7 @@ public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Except @Override public void invalidate(K key) { if (this.cache.containsKey(key)) { - eventDispatcher.dispatchRemovalEvent( - new StoreAwareCacheRemovalNotification<>(key, null, RemovalReason.INVALIDATED, CacheStoreType.ON_HEAP) - ); + eventListener.onRemoval(new StoreAwareCacheRemovalNotification<>(key, null, RemovalReason.INVALIDATED, CacheStoreType.ON_HEAP)); } this.cache.remove(key); @@ -711,7 +654,7 @@ public void invalidate(K key) { @Override public V compute(K key, LoadAwareCacheLoader loader) throws Exception { if (this.cache.size() >= maxSize) { // If it exceeds, just notify for evict. - eventDispatcher.dispatchRemovalEvent( + eventListener.onRemoval( new StoreAwareCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP) ); return loader.load(key); @@ -723,7 +666,7 @@ public V compute(K key, LoadAwareCacheLoader loader) throws Exception { throw new RuntimeException(e); } })); - eventDispatcher.dispatch(key, value, CacheStoreType.ON_HEAP, EventType.ON_CACHED); + eventListener.onCached(key, value, CacheStoreType.ON_HEAP); return value; } @@ -758,7 +701,7 @@ public static class Builder extends StoreAwareCacheBuilder { @Override public StoreAwareCache build() { - return new MockOnHeapCache<>(maxSize, this.getListenerConfiguration()); + return new MockOnHeapCache<>(maxSize, this.getEventListener()); } public Builder setMaxSize(int maxSize) { diff --git a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java index a55475897c162..add1dfc348a8b 100644 --- a/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java +++ b/server/src/test/java/org/opensearch/indices/IndicesServiceCloseTests.java @@ -45,7 +45,6 @@ import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.routing.allocation.DiskThresholdSettings; import org.opensearch.common.cache.RemovalNotification; -import org.opensearch.common.cache.store.enums.CacheStoreType; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.OpenSearchExecutors; import org.opensearch.core.common.bytes.BytesArray; @@ -381,7 +380,7 @@ public long ramBytesUsed() { } @Override - public void onCached(Key key, BytesReference value, CacheStoreType tierType) {} + public void onCached(Key key, BytesReference value) {} @Override public boolean isOpen() { @@ -394,10 +393,10 @@ public Object getCacheIdentity() { } @Override - public void onHit(CacheStoreType tierType) {} + public void onHit() {} @Override - public void onMiss(CacheStoreType tierType) {} + public void onMiss() {} @Override public void onRemoval(RemovalNotification notification) {} From c9cf2c031934decf655af7299700d99e786a7ba9 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 21 Dec 2023 13:53:25 -0800 Subject: [PATCH 12/19] Adding opensearch.internal tag for LoadAwareCacheLoader Signed-off-by: Sagar Upadhyaya --- .../java/org/opensearch/common/cache/LoadAwareCacheLoader.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/org/opensearch/common/cache/LoadAwareCacheLoader.java b/server/src/main/java/org/opensearch/common/cache/LoadAwareCacheLoader.java index 1cc47f93bdf7d..57aa4aa39c782 100644 --- a/server/src/main/java/org/opensearch/common/cache/LoadAwareCacheLoader.java +++ b/server/src/main/java/org/opensearch/common/cache/LoadAwareCacheLoader.java @@ -12,6 +12,8 @@ * Extends a cache loader with awareness of whether the data is loaded or not. * @param Type of key. * @param Type of value. + * + * @opensearch.internal */ public interface LoadAwareCacheLoader extends CacheLoader { boolean isLoaded(); From 731825217620725577c6fe762e2046bcc7d941d9 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 28 Dec 2023 08:38:34 -0800 Subject: [PATCH 13/19] Fixing thread safety issue Signed-off-by: Sagar Upadhyaya --- .../cache/tier/TieredSpilloverCache.java | 57 +++-- .../cache/tier/TieredSpilloverCacheTests.java | 202 ++++++++++++++++-- 2 files changed, 232 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java index cd572f1706332..a1fe9d94de9ae 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java @@ -17,6 +17,7 @@ import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; import org.opensearch.common.cache.store.enums.CacheStoreType; import org.opensearch.common.cache.store.listeners.StoreAwareCacheEventListener; +import org.opensearch.common.util.concurrent.ReleasableLock; import org.opensearch.common.util.iterable.Iterables; import java.util.Arrays; @@ -24,6 +25,8 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; /** @@ -38,9 +41,13 @@ */ public class TieredSpilloverCache implements TieredCache, StoreAwareCacheEventListener { + // TODO: Remove optional when diskCache implementation is integrated. private final Optional> onDiskCache; private final StoreAwareCache onHeapCache; private final StoreAwareCacheEventListener listener; + ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + ReleasableLock readLock = new ReleasableLock(readWriteLock.readLock()); + ReleasableLock writeLock = new ReleasableLock(readWriteLock.writeLock()); /** * Maintains caching tiers in ascending order of cache latency. @@ -59,6 +66,16 @@ public class TieredSpilloverCache implements TieredCache, StoreAware this.cacheList = this.onDiskCache.map(diskTier -> Arrays.asList(this.onHeapCache, diskTier)).orElse(List.of(this.onHeapCache)); } + // Package private for testing + StoreAwareCache getOnHeapCache() { + return onHeapCache; + } + + // Package private for testing + Optional> getOnDiskCache() { + return onDiskCache; + } + @Override public V get(K key) { StoreAwareCacheValue cacheValue = getValueFromTieredCache().apply(key); @@ -70,16 +87,22 @@ public V get(K key) { @Override public void put(K key, V value) { - onHeapCache.put(key, value); + try (ReleasableLock ignore = writeLock.acquire()) { + onHeapCache.put(key, value); + } } @Override public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { StoreAwareCacheValue cacheValue = getValueFromTieredCache().apply(key); if (cacheValue == null) { - // Add the value to the onHeap cache. Any items if evicted will be moved to lower tier. - V value = onHeapCache.compute(key, loader); - return value; + // Add the value to the onHeap cache. We are calling computeIfAbsent which does another get inside. + // This is needed as there can be many requests for the same key at the same time and we only want to load + // the value once. + try (ReleasableLock ignore = writeLock.acquire()) { + V value = onHeapCache.computeIfAbsent(key, loader); + return value; + } } return cacheValue.getValue(); } @@ -89,8 +112,10 @@ public void invalidate(K key) { // We are trying to invalidate the key from all caches though it would be present in only of them. // Doing this as we don't know where it is located. We could do a get from both and check that, but what will // also trigger a hit/miss listener event, so ignoring it for now. - for (StoreAwareCache storeAwareCache : cacheList) { - storeAwareCache.invalidate(key); + try (ReleasableLock ignore = writeLock.acquire()) { + for (StoreAwareCache storeAwareCache : cacheList) { + storeAwareCache.invalidate(key); + } } } @@ -101,8 +126,10 @@ public V compute(K key, LoadAwareCacheLoader loader) throws Exception { @Override public void invalidateAll() { - for (StoreAwareCache storeAwareCache : cacheList) { - storeAwareCache.invalidateAll(); + try (ReleasableLock ignore = writeLock.acquire()) { + for (StoreAwareCache storeAwareCache : cacheList) { + storeAwareCache.invalidateAll(); + } } } @@ -174,7 +201,9 @@ public void onRemoval(StoreAwareCacheRemovalNotification notification) { || RemovalReason.CAPACITY.equals(notification.getRemovalReason())) { switch (notification.getCacheStoreType()) { case ON_HEAP: - onDiskCache.ifPresent(diskTier -> { diskTier.put(notification.getKey(), notification.getValue()); }); + try (ReleasableLock ignore = writeLock.acquire()) { + onDiskCache.ifPresent(diskTier -> { diskTier.put(notification.getKey(), notification.getValue()); }); + } break; default: break; @@ -195,10 +224,12 @@ public void onCached(K key, V value, CacheStoreType cacheStoreType) { private Function> getValueFromTieredCache() { return key -> { - for (StoreAwareCache storeAwareCache : cacheList) { - V value = storeAwareCache.get(key); - if (value != null) { - return new StoreAwareCacheValue<>(value, storeAwareCache.getTierType()); + try (ReleasableLock ignore = readLock.acquire()) { + for (StoreAwareCache storeAwareCache : cacheList) { + V value = storeAwareCache.get(key); + if (value != null) { + return new StoreAwareCacheValue<>(value, storeAwareCache.getTierType()); + } } } return null; diff --git a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java index c43de6cf50b89..270c4ca059b9f 100644 --- a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java +++ b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java @@ -10,6 +10,7 @@ import org.opensearch.common.cache.LoadAwareCacheLoader; import org.opensearch.common.cache.RemovalReason; +import org.opensearch.common.cache.store.OpenSearchOnHeapCache; import org.opensearch.common.cache.store.StoreAwareCache; import org.opensearch.common.cache.store.StoreAwareCacheRemovalNotification; import org.opensearch.common.cache.store.builders.StoreAwareCacheBuilder; @@ -24,6 +25,10 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Phaser; +import java.util.concurrent.atomic.AtomicReference; public class TieredSpilloverCacheTests extends OpenSearchTestCase { @@ -33,7 +38,8 @@ public void testComputeIfAbsentWithoutAnyOnHeapCacheEviction() throws Exception TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, randomIntBetween(1, 4), - eventListener + eventListener, + 0 ); int numOfItems1 = randomIntBetween(1, onHeapCacheSize / 2 - 1); List keys = new ArrayList<>(); @@ -77,7 +83,8 @@ public void testComputeIfAbsentWithEvictionsFromOnHeapCache() throws Exception { TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListener + eventListener, + 0 ); // Put values in cache more than it's size and cause evictions from onHeap. @@ -144,7 +151,8 @@ public void testComputeIfAbsentWithEvictionsFromBothTier() throws Exception { TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListener + eventListener, + 0 ); int numOfItems = randomIntBetween(totalSize + 1, totalSize * 3); @@ -165,7 +173,8 @@ public void testGetAndCount() throws Exception { TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListener + eventListener, + 0 ); int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); @@ -230,7 +239,8 @@ public void testPut() { TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListener + eventListener, + 0 ); String key = UUID.randomUUID().toString(); String value = UUID.randomUUID().toString(); @@ -247,7 +257,8 @@ public void testInvalidate() { TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListener + eventListener, + 0 ); String key = UUID.randomUUID().toString(); String value = UUID.randomUUID().toString(); @@ -278,7 +289,8 @@ public void testComputeWithoutAnyOnHeapEvictions() throws Exception { TieredSpilloverCache spilloverStrategyService = intializeTieredSpilloverCache( onHeapCacheSize, randomIntBetween(1, 4), - eventListener + eventListener, + 0 ); int numOfItems1 = randomIntBetween(1, onHeapCacheSize / 2 - 1); List keys = new ArrayList<>(); @@ -301,7 +313,8 @@ public void testCacheKeys() throws Exception { TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListener + eventListener, + 0 ); // Put values in cache more than it's size and cause evictions from onHeap. int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); @@ -354,7 +367,8 @@ public void testRefresh() { TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListener + eventListener, + 0 ); tieredSpilloverCache.refresh(CacheStoreType.ON_HEAP); tieredSpilloverCache.refresh(CacheStoreType.DISK); @@ -370,7 +384,8 @@ public void testInvalidateAll() throws Exception { TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( onHeapCacheSize, diskCacheSize, - eventListener + eventListener, + 0 ); // Put values in cache more than it's size and cause evictions from onHeap. int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); @@ -392,6 +407,149 @@ public void testInvalidateAll() throws Exception { assertEquals(0, tieredSpilloverCache.count()); } + public void testComputeIfAbsentConcurrently() throws Exception { + int onHeapCacheSize = randomIntBetween(100, 300); + int diskCacheSize = randomIntBetween(200, 400); + + MockCacheEventListener eventListener = new MockCacheEventListener<>(); + + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListener, + 0 + ); + + int numberOfSameKeys = randomIntBetween(10, onHeapCacheSize - 1); + String key = UUID.randomUUID().toString(); + String value = UUID.randomUUID().toString(); + + Thread[] threads = new Thread[numberOfSameKeys]; + Phaser phaser = new Phaser(numberOfSameKeys + 1); + CountDownLatch countDownLatch = new CountDownLatch(numberOfSameKeys); // To wait for all threads to finish. + + List> loadAwareCacheLoaderList = new CopyOnWriteArrayList<>(); + + for (int i = 0; i < numberOfSameKeys; i++) { + threads[i] = new Thread(() -> { + try { + LoadAwareCacheLoader loadAwareCacheLoader = new LoadAwareCacheLoader() { + boolean isLoaded = false; + + @Override + public boolean isLoaded() { + return isLoaded; + } + + @Override + public Object load(Object key) throws Exception { + isLoaded = true; + return value; + } + }; + loadAwareCacheLoaderList.add(loadAwareCacheLoader); + phaser.arriveAndAwaitAdvance(); + tieredSpilloverCache.computeIfAbsent(key, loadAwareCacheLoader); + } catch (Exception e) { + throw new RuntimeException(e); + } + countDownLatch.countDown(); + }); + threads[i].start(); + } + phaser.arriveAndAwaitAdvance(); + countDownLatch.await(); // Wait for rest of tasks to be cancelled. + int numberOfTimesKeyLoaded = 0; + assertEquals(numberOfSameKeys, loadAwareCacheLoaderList.size()); + for (int i = 0; i < loadAwareCacheLoaderList.size(); i++) { + LoadAwareCacheLoader loader = loadAwareCacheLoaderList.get(i); + if (loader.isLoaded()) { + numberOfTimesKeyLoaded++; + } + } + assertEquals(1, numberOfTimesKeyLoaded); // It should be loaded only once. + } + + public void testConcurrencyForEvictionFlow() throws Exception { + int diskCacheSize = randomIntBetween(450, 800); + + MockCacheEventListener eventListener = new MockCacheEventListener<>(); + + StoreAwareCacheBuilder cacheBuilder = new OpenSearchOnHeapCache.Builder().setMaximumWeightInBytes( + 200 + ).setWeigher((k, v) -> 150); + + StoreAwareCacheBuilder diskCacheBuilder = new MockOnDiskCache.Builder().setMaxSize(diskCacheSize) + .setDeliberateDelay(500); + + TieredSpilloverCache tieredSpilloverCache = new TieredSpilloverCache.Builder() + .setOnHeapCacheBuilder(cacheBuilder) + .setOnDiskCacheBuilder(diskCacheBuilder) + .setListener(eventListener) + .build(); + + String keyToBeEvicted = "key1"; + String secondKey = "key2"; + + // Put first key on tiered cache. Will go into onHeap cache. + tieredSpilloverCache.computeIfAbsent(keyToBeEvicted, new LoadAwareCacheLoader<>() { + @Override + public boolean isLoaded() { + return false; + } + + @Override + public String load(String key) throws Exception { + return UUID.randomUUID().toString(); + } + }); + CountDownLatch countDownLatch = new CountDownLatch(1); + CountDownLatch countDownLatch1 = new CountDownLatch(1); + // Put second key on tiered cache. Will cause eviction of first key from onHeap cache and should go into + // disk cache. + Thread thread = new Thread(() -> { + try { + tieredSpilloverCache.computeIfAbsent(secondKey, new LoadAwareCacheLoader() { + @Override + public boolean isLoaded() { + return false; + } + + @Override + public String load(String key) throws Exception { + return UUID.randomUUID().toString(); + } + }); + countDownLatch1.countDown(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + thread.start(); + Thread.sleep(100); // Delay for cache for eviction to occur. + StoreAwareCache onDiskCache = tieredSpilloverCache.getOnDiskCache().get(); + + // Now on a different thread, try to get key(above one which got evicted) from tiered cache. We expect this + // should return not null value as it should be present on diskCache. + AtomicReference actualValue = new AtomicReference<>(); + Thread thread1 = new Thread(() -> { + try { + actualValue.set(tieredSpilloverCache.get(keyToBeEvicted)); + } catch (Exception e) { + throw new RuntimeException(e); + } + countDownLatch.countDown(); + }); + thread1.start(); + countDownLatch.await(); + assertNotNull(actualValue.get()); + countDownLatch1.await(); + assertEquals(1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count()); + assertEquals(1, tieredSpilloverCache.getOnHeapCache().count()); + assertEquals(1, onDiskCache.count()); + assertNotNull(onDiskCache.get(keyToBeEvicted)); + } + class MockCacheEventListener implements StoreAwareCacheEventListener { EnumMap enumMap = new EnumMap<>(CacheStoreType.class); @@ -455,9 +613,11 @@ public boolean isLoaded() { private TieredSpilloverCache intializeTieredSpilloverCache( int onHeapCacheSize, int diksCacheSize, - StoreAwareCacheEventListener eventListener + StoreAwareCacheEventListener eventListener, + long diskDeliberateDelay ) { - StoreAwareCacheBuilder diskCacheBuilder = new MockOnDiskCache.Builder().setMaxSize(diksCacheSize); + StoreAwareCacheBuilder diskCacheBuilder = new MockOnDiskCache.Builder().setMaxSize(diksCacheSize) + .setDeliberateDelay(diskDeliberateDelay); StoreAwareCacheBuilder onHeapCacheBuilder = new MockOnHeapCache.Builder().setMaxSize( onHeapCacheSize ); @@ -472,11 +632,14 @@ class MockOnDiskCache implements StoreAwareCache { Map cache; int maxSize; + + long delay; StoreAwareCacheEventListener eventListener; - MockOnDiskCache(int maxSize, StoreAwareCacheEventListener eventListener) { + MockOnDiskCache(int maxSize, StoreAwareCacheEventListener eventListener, long delay) { this.maxSize = maxSize; this.eventListener = eventListener; + this.delay = delay; this.cache = new ConcurrentHashMap(); } @@ -497,6 +660,11 @@ public void put(K key, V value) { eventListener.onRemoval(new StoreAwareCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.DISK)); return; } + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } this.cache.put(key, value); eventListener.onCached(key, value, CacheStoreType.DISK); } @@ -572,16 +740,22 @@ public CacheStoreType getTierType() { public static class Builder extends StoreAwareCacheBuilder { int maxSize; + long delay; @Override public StoreAwareCache build() { - return new MockOnDiskCache(maxSize, this.getEventListener()); + return new MockOnDiskCache(maxSize, this.getEventListener(), delay); } public Builder setMaxSize(int maxSize) { this.maxSize = maxSize; return this; } + + public Builder setDeliberateDelay(long millis) { + this.delay = millis; + return this; + } } } From 79dd693ff59411cfa6527797ab5d9b7e7529a344 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 28 Dec 2023 12:40:01 -0800 Subject: [PATCH 14/19] Remove compute function and event listener logic change for TieredCache Signed-off-by: Sagar Upadhyaya --- .../org/opensearch/common/cache/ICache.java | 2 - .../cache/store/OpenSearchOnHeapCache.java | 7 - .../cache/tier/TieredSpilloverCache.java | 48 +++++-- .../cache/tier/TieredSpilloverCacheTests.java | 127 ++++++------------ 4 files changed, 74 insertions(+), 110 deletions(-) diff --git a/server/src/main/java/org/opensearch/common/cache/ICache.java b/server/src/main/java/org/opensearch/common/cache/ICache.java index aa868cdf619ac..c6ea5fca1a8fe 100644 --- a/server/src/main/java/org/opensearch/common/cache/ICache.java +++ b/server/src/main/java/org/opensearch/common/cache/ICache.java @@ -24,8 +24,6 @@ public interface ICache { void invalidate(K key); - V compute(K key, LoadAwareCacheLoader loader) throws Exception; - void invalidateAll(); Iterable keys(); diff --git a/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java b/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java index 408e2ef5bc70d..c497c8dbb7ea9 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java +++ b/server/src/main/java/org/opensearch/common/cache/store/OpenSearchOnHeapCache.java @@ -76,13 +76,6 @@ public void invalidate(K key) { cache.invalidate(key); } - @Override - public V compute(K key, LoadAwareCacheLoader loader) throws Exception { - V value = cache.compute(key, key1 -> loader.load(key)); - eventListener.onCached(key, value, CacheStoreType.ON_HEAP); - return value; - } - @Override public void invalidateAll() { cache.invalidateAll(); diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java index a1fe9d94de9ae..f9d7726af16f0 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java @@ -78,7 +78,7 @@ Optional> getOnDiskCache() { @Override public V get(K key) { - StoreAwareCacheValue cacheValue = getValueFromTieredCache().apply(key); + StoreAwareCacheValue cacheValue = getValueFromTieredCache(true).apply(key); if (cacheValue == null) { return null; } @@ -89,20 +89,37 @@ public V get(K key) { public void put(K key, V value) { try (ReleasableLock ignore = writeLock.acquire()) { onHeapCache.put(key, value); + listener.onCached(key, value, CacheStoreType.ON_HEAP); } } @Override public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { - StoreAwareCacheValue cacheValue = getValueFromTieredCache().apply(key); + // We are skipping calling event listeners at this step as we do another get inside below computeIfAbsent. + // Where we might end up calling onMiss twice for a key not present in onHeap cache. + // Similary we might end up calling both onMiss and onHit for a key, in case we are received concurrent + // requests for the same key which requires loading only once. + StoreAwareCacheValue cacheValue = getValueFromTieredCache(false).apply(key); if (cacheValue == null) { // Add the value to the onHeap cache. We are calling computeIfAbsent which does another get inside. // This is needed as there can be many requests for the same key at the same time and we only want to load // the value once. + V value = null; try (ReleasableLock ignore = writeLock.acquire()) { - V value = onHeapCache.computeIfAbsent(key, loader); - return value; + value = onHeapCache.computeIfAbsent(key, loader); } + if (loader.isLoaded()) { + listener.onMiss(key, CacheStoreType.ON_HEAP); + onDiskCache.ifPresent(diskTier -> listener.onMiss(key, CacheStoreType.DISK)); + listener.onCached(key, value, CacheStoreType.ON_HEAP); + } else { + listener.onHit(key, value, CacheStoreType.ON_HEAP); + } + return value; + } + listener.onHit(key, cacheValue.getValue(), cacheValue.getSource()); + if (cacheValue.getSource().equals(CacheStoreType.DISK)) { + listener.onMiss(key, CacheStoreType.ON_HEAP); } return cacheValue.getValue(); } @@ -119,11 +136,6 @@ public void invalidate(K key) { } } - @Override - public V compute(K key, LoadAwareCacheLoader loader) throws Exception { - return onHeapCache.compute(key, loader); - } - @Override public void invalidateAll() { try (ReleasableLock ignore = writeLock.acquire()) { @@ -192,7 +204,7 @@ public void refresh(CacheStoreType type) { @Override public void onMiss(K key, CacheStoreType cacheStoreType) { - listener.onMiss(key, cacheStoreType); + // Misses for tiered cache are tracked here itself. } @Override @@ -204,6 +216,9 @@ public void onRemoval(StoreAwareCacheRemovalNotification notification) { try (ReleasableLock ignore = writeLock.acquire()) { onDiskCache.ifPresent(diskTier -> { diskTier.put(notification.getKey(), notification.getValue()); }); } + onDiskCache.ifPresent( + diskTier -> listener.onCached(notification.getKey(), notification.getValue(), CacheStoreType.DISK) + ); break; default: break; @@ -214,21 +229,28 @@ public void onRemoval(StoreAwareCacheRemovalNotification notification) { @Override public void onHit(K key, V value, CacheStoreType cacheStoreType) { - listener.onHit(key, value, cacheStoreType); + // Hits for tiered cache are tracked here itself. } @Override public void onCached(K key, V value, CacheStoreType cacheStoreType) { - listener.onCached(key, value, cacheStoreType); + // onCached events for tiered cache are tracked here itself. } - private Function> getValueFromTieredCache() { + private Function> getValueFromTieredCache(boolean triggerEventListener) { return key -> { try (ReleasableLock ignore = readLock.acquire()) { for (StoreAwareCache storeAwareCache : cacheList) { V value = storeAwareCache.get(key); if (value != null) { + if (triggerEventListener) { + listener.onHit(key, value, storeAwareCache.getTierType()); + } return new StoreAwareCacheValue<>(value, storeAwareCache.getTierType()); + } else { + if (triggerEventListener) { + listener.onMiss(key, storeAwareCache.getTierType()); + } } } } diff --git a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java index 270c4ca059b9f..0dbec1db302f4 100644 --- a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java +++ b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java @@ -80,12 +80,18 @@ public void testComputeIfAbsentWithEvictionsFromOnHeapCache() throws Exception { int diskCacheSize = randomIntBetween(60, 100); int totalSize = onHeapCacheSize + diskCacheSize; MockCacheEventListener eventListener = new MockCacheEventListener(); - TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( - onHeapCacheSize, - diskCacheSize, - eventListener, - 0 - ); + StoreAwareCacheBuilder cacheBuilder = new OpenSearchOnHeapCache.Builder().setMaximumWeightInBytes( + onHeapCacheSize * 50 + ).setWeigher((k, v) -> 50); // Will support onHeapCacheSize entries. + + StoreAwareCacheBuilder diskCacheBuilder = new MockOnDiskCache.Builder().setMaxSize(diskCacheSize) + .setDeliberateDelay(0); + + TieredSpilloverCache tieredSpilloverCache = new TieredSpilloverCache.Builder() + .setOnHeapCacheBuilder(cacheBuilder) + .setOnDiskCacheBuilder(diskCacheBuilder) + .setListener(eventListener) + .build(); // Put values in cache more than it's size and cause evictions from onHeap. int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); @@ -93,24 +99,25 @@ public void testComputeIfAbsentWithEvictionsFromOnHeapCache() throws Exception { List diskTierKeys = new ArrayList<>(); for (int iter = 0; iter < numOfItems1; iter++) { String key = UUID.randomUUID().toString(); - if (iter > (onHeapCacheSize - 1)) { - // All these are bound to go to disk based cache. - diskTierKeys.add(key); - } else { - onHeapKeys.add(key); - } LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); tieredSpilloverCache.computeIfAbsent(key, tieredCacheLoader); } + long actualDiskCacheSize = tieredSpilloverCache.getOnDiskCache().get().count(); assertEquals(numOfItems1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); assertEquals(0, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); - assertTrue(eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count() > 0); + assertEquals(actualDiskCacheSize, eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count()); assertEquals( eventListener.enumMap.get(CacheStoreType.ON_HEAP).evictionsMetric.count(), eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count() ); - assertEquals(diskTierKeys.size(), eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count()); + assertEquals(actualDiskCacheSize, eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count()); + + tieredSpilloverCache.cacheKeys(CacheStoreType.ON_HEAP).forEach(onHeapKeys::add); + tieredSpilloverCache.cacheKeys(CacheStoreType.DISK).forEach(diskTierKeys::add); + + assertEquals(tieredSpilloverCache.getOnHeapCache().count(), onHeapKeys.size()); + assertEquals(tieredSpilloverCache.getOnDiskCache().get().count(), diskTierKeys.size()); // Try to hit cache again with some randomization. int numOfItems2 = randomIntBetween(50, 200); @@ -118,23 +125,26 @@ public void testComputeIfAbsentWithEvictionsFromOnHeapCache() throws Exception { int diskCacheHit = 0; int cacheMiss = 0; for (int iter = 0; iter < numOfItems2; iter++) { - if (randomBoolean()) { - if (randomBoolean()) { // Hit cache with key stored in onHeap cache. - onHeapCacheHit++; - int index = randomIntBetween(0, onHeapKeys.size() - 1); - tieredSpilloverCache.computeIfAbsent(onHeapKeys.get(index), getLoadAwareCacheLoader()); - } else { // Hit cache with key stored in disk cache. - diskCacheHit++; - int index = randomIntBetween(0, diskTierKeys.size() - 1); - tieredSpilloverCache.computeIfAbsent(diskTierKeys.get(index), getLoadAwareCacheLoader()); - } - } else { - // Hit cache with randomized key which is expected to miss cache always. - LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); - tieredSpilloverCache.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); - cacheMiss++; + if (randomBoolean()) { // Hit cache with key stored in onHeap cache. + onHeapCacheHit++; + int index = randomIntBetween(0, onHeapKeys.size() - 1); + LoadAwareCacheLoader loadAwareCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(onHeapKeys.get(index), loadAwareCacheLoader); + assertFalse(loadAwareCacheLoader.isLoaded()); + } else { // Hit cache with key stored in disk cache. + diskCacheHit++; + int index = randomIntBetween(0, diskTierKeys.size() - 1); + LoadAwareCacheLoader loadAwareCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(diskTierKeys.get(index), loadAwareCacheLoader); + assertFalse(loadAwareCacheLoader.isLoaded()); } } + for (int iter = 0; iter < randomIntBetween(50, 200); iter++) { + // Hit cache with randomized key which is expected to miss cache always. + LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); + tieredSpilloverCache.computeIfAbsent(UUID.randomUUID().toString(), tieredCacheLoader); + cacheMiss++; + } // On heap cache misses would also include diskCacheHits as it means it missed onHeap cache. assertEquals(numOfItems1 + cacheMiss + diskCacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).missCount.count()); assertEquals(onHeapCacheHit, eventListener.enumMap.get(CacheStoreType.ON_HEAP).hitCount.count()); @@ -283,27 +293,6 @@ public void testInvalidate() { assertEquals(1, tieredSpilloverCache.count()); } - public void testComputeWithoutAnyOnHeapEvictions() throws Exception { - int onHeapCacheSize = randomIntBetween(10, 30); - MockCacheEventListener eventListener = new MockCacheEventListener(); - TieredSpilloverCache spilloverStrategyService = intializeTieredSpilloverCache( - onHeapCacheSize, - randomIntBetween(1, 4), - eventListener, - 0 - ); - int numOfItems1 = randomIntBetween(1, onHeapCacheSize / 2 - 1); - List keys = new ArrayList<>(); - // Put values in cache. - for (int iter = 0; iter < numOfItems1; iter++) { - String key = UUID.randomUUID().toString(); - keys.add(key); - LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); - spilloverStrategyService.compute(key, tieredCacheLoader); - } - assertEquals(numOfItems1, eventListener.enumMap.get(CacheStoreType.ON_HEAP).cachedCount.count()); - } - public void testCacheKeys() throws Exception { int onHeapCacheSize = randomIntBetween(10, 30); int diskCacheSize = randomIntBetween(60, 100); @@ -695,25 +684,6 @@ public void invalidate(K key) { this.cache.remove(key); } - @Override - public V compute(K key, LoadAwareCacheLoader loader) throws Exception { - if (this.cache.size() >= maxSize) { // If it exceeds, just notify for evict. - eventListener.onRemoval( - new StoreAwareCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.DISK) - ); - return loader.load(key); - } - V value = this.cache.compute(key, ((k, v) -> { - try { - return loader.load(key); - } catch (Exception e) { - throw new RuntimeException(e); - } - })); - eventListener.onCached(key, value, CacheStoreType.DISK); - return value; - } - @Override public void invalidateAll() { this.cache.clear(); @@ -825,25 +795,6 @@ public void invalidate(K key) { } - @Override - public V compute(K key, LoadAwareCacheLoader loader) throws Exception { - if (this.cache.size() >= maxSize) { // If it exceeds, just notify for evict. - eventListener.onRemoval( - new StoreAwareCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP) - ); - return loader.load(key); - } - V value = this.cache.compute(key, ((k, v) -> { - try { - return loader.load(key); - } catch (Exception e) { - throw new RuntimeException(e); - } - })); - eventListener.onCached(key, value, CacheStoreType.ON_HEAP); - return value; - } - @Override public void invalidateAll() { this.cache.clear(); From 7c10adf6294cba8beaabe625d1261740b9fd4b56 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Thu, 28 Dec 2023 12:42:49 -0800 Subject: [PATCH 15/19] Making Cache.compute function private Signed-off-by: Sagar Upadhyaya --- server/src/main/java/org/opensearch/common/cache/Cache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/opensearch/common/cache/Cache.java b/server/src/main/java/org/opensearch/common/cache/Cache.java index f7d9a4ee871e2..d8aa4e93735e6 100644 --- a/server/src/main/java/org/opensearch/common/cache/Cache.java +++ b/server/src/main/java/org/opensearch/common/cache/Cache.java @@ -429,7 +429,7 @@ public V computeIfAbsent(K key, CacheLoader loader) throws ExecutionExcept return value; } - public V compute(K key, CacheLoader loader) throws ExecutionException { + private V compute(K key, CacheLoader loader) throws ExecutionException { long now = now(); // we need to synchronize loading of a value for a given key; however, holding the segment lock while // invoking load can lead to deadlock against another thread due to dependent key loading; therefore, we From 1832df9c075b1f80a5007a48841db5002015fe59 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Tue, 2 Jan 2024 23:49:31 -0800 Subject: [PATCH 16/19] Adding javadoc and more test for cache.put Signed-off-by: Sagar Upadhyaya --- .../cache/store/StoreAwareCacheValue.java | 2 +- .../cache/tier/TieredSpilloverCache.java | 22 +- .../cache/tier/TieredSpilloverCacheTests.java | 207 +++++++----------- 3 files changed, 100 insertions(+), 131 deletions(-) diff --git a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java index e911b1041ba31..4fbbbbfebfaa7 100644 --- a/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java +++ b/server/src/main/java/org/opensearch/common/cache/store/StoreAwareCacheValue.java @@ -29,7 +29,7 @@ public V getValue() { return value; } - public CacheStoreType getSource() { + public CacheStoreType getCacheStoreType() { return source; } } diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java index f9d7726af16f0..26fa12ee4b726 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java @@ -97,7 +97,7 @@ public void put(K key, V value) { public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { // We are skipping calling event listeners at this step as we do another get inside below computeIfAbsent. // Where we might end up calling onMiss twice for a key not present in onHeap cache. - // Similary we might end up calling both onMiss and onHit for a key, in case we are received concurrent + // Similary we might end up calling both onMiss and onHit for a key, in case we are receiving concurrent // requests for the same key which requires loading only once. StoreAwareCacheValue cacheValue = getValueFromTieredCache(false).apply(key); if (cacheValue == null) { @@ -117,8 +117,8 @@ public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Except } return value; } - listener.onHit(key, cacheValue.getValue(), cacheValue.getSource()); - if (cacheValue.getSource().equals(CacheStoreType.DISK)) { + listener.onHit(key, cacheValue.getValue(), cacheValue.getCacheStoreType()); + if (cacheValue.getCacheStoreType().equals(CacheStoreType.DISK)) { listener.onMiss(key, CacheStoreType.ON_HEAP); } return cacheValue.getValue(); @@ -145,6 +145,10 @@ public void invalidateAll() { } } + /** + * Provides an iteration over both onHeap and disk keys. This is not protected from any mutations to the cache. + * @return An iterable over (onHeap + disk) keys + */ @Override public Iterable keys() { Iterable onDiskKeysIterable; @@ -167,11 +171,19 @@ public long count() { @Override public void refresh() { - for (StoreAwareCache storeAwareCache : cacheList) { - storeAwareCache.refresh(); + try (ReleasableLock ignore = writeLock.acquire()) { + for (StoreAwareCache storeAwareCache : cacheList) { + storeAwareCache.refresh(); + } } } + /** + * Provides an iteration over keys based on desired on cacheStoreType. This is not protected from any mutations + * to the cache. + * @param type Type of cacheStoreType + * @return An iterable over desired CacheStoreType keys + */ @Override public Iterable cacheKeys(CacheStoreType type) { switch (type) { diff --git a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java index 0dbec1db302f4..d412da2cd497c 100644 --- a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java +++ b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java @@ -222,9 +222,9 @@ public void testWithDiskTierNull() throws Exception { int onHeapCacheSize = randomIntBetween(10, 30); MockCacheEventListener eventListener = new MockCacheEventListener(); - StoreAwareCacheBuilder onHeapCacheBuilder = new MockOnHeapCache.Builder().setMaxSize( - onHeapCacheSize - ); + StoreAwareCacheBuilder onHeapCacheBuilder = new OpenSearchOnHeapCache.Builder() + .setMaximumWeightInBytes(onHeapCacheSize * 20) + .setWeigher((k, v) -> 20); // Will support upto onHeapCacheSize entries TieredSpilloverCache tieredSpilloverCache = new TieredSpilloverCache.Builder() .setOnHeapCacheBuilder(onHeapCacheBuilder) .setListener(eventListener) @@ -259,6 +259,69 @@ public void testPut() { assertEquals(1, tieredSpilloverCache.count()); } + public void testPutAndVerifyNewItemsArePresentOnHeapCache() throws Exception { + int onHeapCacheSize = randomIntBetween(200, 400); + int diskCacheSize = randomIntBetween(450, 800); + + MockCacheEventListener eventListener = new MockCacheEventListener<>(); + + TieredSpilloverCache tieredSpilloverCache = intializeTieredSpilloverCache( + onHeapCacheSize, + diskCacheSize, + eventListener, + 0 + ); + + for (int i = 0; i < onHeapCacheSize; i++) { + tieredSpilloverCache.computeIfAbsent(UUID.randomUUID().toString(), new LoadAwareCacheLoader<>() { + @Override + public boolean isLoaded() { + return false; + } + + @Override + public String load(String key) throws Exception { + return UUID.randomUUID().toString(); + } + }); + } + + assertEquals(onHeapCacheSize, tieredSpilloverCache.getOnHeapCache().count()); + assertEquals(0, tieredSpilloverCache.getOnDiskCache().get().count()); + + // Again try to put OnHeap cache capacity amount of new items. + List newKeyList = new ArrayList<>(); + for (int i = 0; i < onHeapCacheSize; i++) { + newKeyList.add(UUID.randomUUID().toString()); + } + + for (int i = 0; i < newKeyList.size(); i++) { + tieredSpilloverCache.computeIfAbsent(newKeyList.get(i), new LoadAwareCacheLoader<>() { + @Override + public boolean isLoaded() { + return false; + } + + @Override + public String load(String key) { + return UUID.randomUUID().toString(); + } + }); + } + + // Verify that new items are part of onHeap cache. + List actualOnHeapCacheKeys = new ArrayList<>(); + tieredSpilloverCache.cacheKeys(CacheStoreType.ON_HEAP).forEach(actualOnHeapCacheKeys::add); + + assertEquals(newKeyList.size(), actualOnHeapCacheKeys.size()); + for (int i = 0; i < actualOnHeapCacheKeys.size(); i++) { + assertTrue(newKeyList.contains(actualOnHeapCacheKeys.get(i))); + } + + assertEquals(onHeapCacheSize, tieredSpilloverCache.getOnHeapCache().count()); + assertEquals(onHeapCacheSize, tieredSpilloverCache.getOnDiskCache().get().count()); + } + public void testInvalidate() { int onHeapCacheSize = 1; int diskCacheSize = 10; @@ -288,7 +351,7 @@ public void testInvalidate() { tieredSpilloverCache.put(key2, UUID.randomUUID().toString()); assertEquals(2, tieredSpilloverCache.count()); // Again invalidate older key - tieredSpilloverCache.invalidate(key2); + tieredSpilloverCache.invalidate(key); assertEquals(1, eventListener.enumMap.get(CacheStoreType.DISK).invalidationMetric.count()); assertEquals(1, tieredSpilloverCache.count()); } @@ -305,21 +368,22 @@ public void testCacheKeys() throws Exception { eventListener, 0 ); - // Put values in cache more than it's size and cause evictions from onHeap. - int numOfItems1 = randomIntBetween(onHeapCacheSize + 1, totalSize); List onHeapKeys = new ArrayList<>(); List diskTierKeys = new ArrayList<>(); - for (int iter = 0; iter < numOfItems1; iter++) { + // During first round add onHeapCacheSize entries. Will go to onHeap cache initially. + for (int i = 0; i < onHeapCacheSize; i++) { String key = UUID.randomUUID().toString(); - if (iter > (onHeapCacheSize - 1)) { - // All these are bound to go to disk based cache. - diskTierKeys.add(key); - } else { - onHeapKeys.add(key); - } - LoadAwareCacheLoader tieredCacheLoader = getLoadAwareCacheLoader(); - tieredSpilloverCache.computeIfAbsent(key, tieredCacheLoader); + diskTierKeys.add(key); + tieredSpilloverCache.computeIfAbsent(key, getLoadAwareCacheLoader()); + } + // In another round, add another onHeapCacheSize entries. These will go to onHeap and above ones will be + // evicted to onDisk cache. + for (int i = 0; i < onHeapCacheSize; i++) { + String key = UUID.randomUUID().toString(); + onHeapKeys.add(key); + tieredSpilloverCache.computeIfAbsent(key, getLoadAwareCacheLoader()); } + List actualOnHeapKeys = new ArrayList<>(); List actualOnDiskKeys = new ArrayList<>(); Iterable onHeapiterable = tieredSpilloverCache.cacheKeys(CacheStoreType.ON_HEAP); @@ -607,9 +671,9 @@ private TieredSpilloverCache intializeTieredSpilloverCache( ) { StoreAwareCacheBuilder diskCacheBuilder = new MockOnDiskCache.Builder().setMaxSize(diksCacheSize) .setDeliberateDelay(diskDeliberateDelay); - StoreAwareCacheBuilder onHeapCacheBuilder = new MockOnHeapCache.Builder().setMaxSize( - onHeapCacheSize - ); + StoreAwareCacheBuilder onHeapCacheBuilder = new OpenSearchOnHeapCache.Builder() + .setMaximumWeightInBytes(onHeapCacheSize * 20) + .setWeigher((k, v) -> 20); // Will support upto onHeapCacheSize entries return new TieredSpilloverCache.Builder().setOnHeapCacheBuilder(onHeapCacheBuilder) .setOnDiskCacheBuilder(diskCacheBuilder) .setListener(eventListener) @@ -728,110 +792,3 @@ public Builder setDeliberateDelay(long millis) { } } } - -class MockOnHeapCache implements StoreAwareCache { - - Map cache; - int maxSize; - StoreAwareCacheEventListener eventListener; - - MockOnHeapCache(int size, StoreAwareCacheEventListener eventListener) { - maxSize = size; - this.cache = new ConcurrentHashMap(); - this.eventListener = eventListener; - } - - @Override - public V get(K key) { - V value = cache.get(key); - if (value != null) { - eventListener.onHit(key, value, CacheStoreType.ON_HEAP); - } else { - eventListener.onMiss(key, CacheStoreType.ON_HEAP); - } - return value; - } - - @Override - public void put(K key, V value) { - if (this.cache.size() >= maxSize) { - eventListener.onRemoval(new StoreAwareCacheRemovalNotification<>(key, value, RemovalReason.EVICTED, CacheStoreType.ON_HEAP)); - return; - } - this.cache.put(key, value); - eventListener.onCached(key, value, CacheStoreType.ON_HEAP); - } - - @Override - public V computeIfAbsent(K key, LoadAwareCacheLoader loader) throws Exception { - if (this.cache.size() >= maxSize) { // If it exceeds, just notify for evict. - eventListener.onRemoval( - new StoreAwareCacheRemovalNotification<>(key, loader.load(key), RemovalReason.EVICTED, CacheStoreType.ON_HEAP) - ); - return loader.load(key); - } - V value = cache.computeIfAbsent(key, key1 -> { - try { - return loader.load(key); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - if (!loader.isLoaded()) { - eventListener.onHit(key, value, CacheStoreType.ON_HEAP); - } else { - eventListener.onMiss(key, CacheStoreType.ON_HEAP); - eventListener.onCached(key, value, CacheStoreType.ON_HEAP); - } - return value; - } - - @Override - public void invalidate(K key) { - if (this.cache.containsKey(key)) { - eventListener.onRemoval(new StoreAwareCacheRemovalNotification<>(key, null, RemovalReason.INVALIDATED, CacheStoreType.ON_HEAP)); - } - this.cache.remove(key); - - } - - @Override - public void invalidateAll() { - this.cache.clear(); - } - - @Override - public Iterable keys() { - return this.cache.keySet(); - } - - @Override - public long count() { - return this.cache.size(); - } - - @Override - public void refresh() { - - } - - @Override - public CacheStoreType getTierType() { - return CacheStoreType.ON_HEAP; - } - - public static class Builder extends StoreAwareCacheBuilder { - - int maxSize; - - @Override - public StoreAwareCache build() { - return new MockOnHeapCache<>(maxSize, this.getEventListener()); - } - - public Builder setMaxSize(int maxSize) { - this.maxSize = maxSize; - return this; - } - } -} From 49aab1f5c1df72bd2c9f2f59602b2844c0a5b0da Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Wed, 3 Jan 2024 01:07:11 -0800 Subject: [PATCH 17/19] Adding write locks to refresh API as well Signed-off-by: Sagar Upadhyaya --- .../common/cache/tier/TieredSpilloverCache.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java index 26fa12ee4b726..4f5c378333f14 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java @@ -204,10 +204,14 @@ public Iterable cacheKeys(CacheStoreType type) { public void refresh(CacheStoreType type) { switch (type) { case ON_HEAP: - onHeapCache.refresh(); + try (ReleasableLock ignore = writeLock.acquire()) { + onHeapCache.refresh(); + } break; case DISK: - onDiskCache.ifPresent(ICache::refresh); + try (ReleasableLock ignore = writeLock.acquire()) { + onDiskCache.ifPresent(ICache::refresh); + } break; default: throw new IllegalArgumentException("Unsupported Cache store type: " + type); From fdda280400ad74f290959f379e97f9d79068396a Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Wed, 3 Jan 2024 10:03:08 -0800 Subject: [PATCH 18/19] Removing unwanted EventType class and refactoring one UT Signed-off-by: Sagar Upadhyaya --- .../cache/store/listeners/EventType.java | 22 ------------------- .../cache/tier/TieredSpilloverCacheTests.java | 20 ++++++----------- 2 files changed, 7 insertions(+), 35 deletions(-) delete mode 100644 server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java diff --git a/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java b/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java deleted file mode 100644 index f81b94ec29858..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/store/listeners/EventType.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.store.listeners; - -/** - * Describes various event types which is used to notify the called via listener. - * - * @opensearch.internal - */ -public enum EventType { - - ON_MISS, - ON_HIT, - ON_CACHED, - ON_REMOVAL; -} diff --git a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java index d412da2cd497c..e7a056b43e460 100644 --- a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java +++ b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java @@ -28,6 +28,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Phaser; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class TieredSpilloverCacheTests extends OpenSearchTestCase { @@ -552,7 +553,7 @@ public boolean isLoaded() { } @Override - public String load(String key) throws Exception { + public String load(String key) { return UUID.randomUUID().toString(); } }); @@ -560,26 +561,19 @@ public String load(String key) throws Exception { CountDownLatch countDownLatch1 = new CountDownLatch(1); // Put second key on tiered cache. Will cause eviction of first key from onHeap cache and should go into // disk cache. + LoadAwareCacheLoader loadAwareCacheLoader = getLoadAwareCacheLoader(); Thread thread = new Thread(() -> { try { - tieredSpilloverCache.computeIfAbsent(secondKey, new LoadAwareCacheLoader() { - @Override - public boolean isLoaded() { - return false; - } - - @Override - public String load(String key) throws Exception { - return UUID.randomUUID().toString(); - } - }); + tieredSpilloverCache.computeIfAbsent(secondKey, loadAwareCacheLoader); countDownLatch1.countDown(); } catch (Exception e) { throw new RuntimeException(e); } }); thread.start(); - Thread.sleep(100); // Delay for cache for eviction to occur. + assertBusy(() -> { assertTrue(loadAwareCacheLoader.isLoaded()); }, 100, TimeUnit.MILLISECONDS); // We wait for new key to be loaded + // after which it eviction flow is + // guaranteed to occur. StoreAwareCache onDiskCache = tieredSpilloverCache.getOnDiskCache().get(); // Now on a different thread, try to get key(above one which got evicted) from tiered cache. We expect this From 9956abcb842b9db1eff7bc9900237ae9ed9bc1a2 Mon Sep 17 00:00:00 2001 From: Sagar Upadhyaya Date: Mon, 8 Jan 2024 09:12:19 -0800 Subject: [PATCH 19/19] Removing TieredCache interface Signed-off-by: Sagar Upadhyaya --- .../common/cache/tier/TieredCache.java | 25 ----------- .../cache/tier/TieredSpilloverCache.java | 42 +------------------ .../cache/tier/TieredSpilloverCacheTests.java | 12 +++--- 3 files changed, 6 insertions(+), 73 deletions(-) delete mode 100644 server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java deleted file mode 100644 index f99d1adb2ab7e..0000000000000 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredCache.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.common.cache.tier; - -import org.opensearch.common.cache.ICache; -import org.opensearch.common.cache.store.enums.CacheStoreType; - -/** - * This represents a cache comprising of multiple tiers/layers. - * @param Type of key - * @param Type of value - * - * @opensearch.experimental - */ -public interface TieredCache extends ICache { - Iterable cacheKeys(CacheStoreType type); - - void refresh(CacheStoreType type); -} diff --git a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java index 4f5c378333f14..8b432c9484aed 100644 --- a/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java +++ b/server/src/main/java/org/opensearch/common/cache/tier/TieredSpilloverCache.java @@ -39,7 +39,7 @@ * * @opensearch.experimental */ -public class TieredSpilloverCache implements TieredCache, StoreAwareCacheEventListener { +public class TieredSpilloverCache implements ICache, StoreAwareCacheEventListener { // TODO: Remove optional when diskCache implementation is integrated. private final Optional> onDiskCache; @@ -178,46 +178,6 @@ public void refresh() { } } - /** - * Provides an iteration over keys based on desired on cacheStoreType. This is not protected from any mutations - * to the cache. - * @param type Type of cacheStoreType - * @return An iterable over desired CacheStoreType keys - */ - @Override - public Iterable cacheKeys(CacheStoreType type) { - switch (type) { - case ON_HEAP: - return onHeapCache.keys(); - case DISK: - if (onDiskCache.isPresent()) { - return onDiskCache.get().keys(); - } else { - return Collections::emptyIterator; - } - default: - throw new IllegalArgumentException("Unsupported Cache store type: " + type); - } - } - - @Override - public void refresh(CacheStoreType type) { - switch (type) { - case ON_HEAP: - try (ReleasableLock ignore = writeLock.acquire()) { - onHeapCache.refresh(); - } - break; - case DISK: - try (ReleasableLock ignore = writeLock.acquire()) { - onDiskCache.ifPresent(ICache::refresh); - } - break; - default: - throw new IllegalArgumentException("Unsupported Cache store type: " + type); - } - } - @Override public void onMiss(K key, CacheStoreType cacheStoreType) { // Misses for tiered cache are tracked here itself. diff --git a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java index e7a056b43e460..eb75244c6f8b1 100644 --- a/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java +++ b/server/src/test/java/org/opensearch/common/cache/tier/TieredSpilloverCacheTests.java @@ -114,8 +114,8 @@ public void testComputeIfAbsentWithEvictionsFromOnHeapCache() throws Exception { ); assertEquals(actualDiskCacheSize, eventListener.enumMap.get(CacheStoreType.DISK).cachedCount.count()); - tieredSpilloverCache.cacheKeys(CacheStoreType.ON_HEAP).forEach(onHeapKeys::add); - tieredSpilloverCache.cacheKeys(CacheStoreType.DISK).forEach(diskTierKeys::add); + tieredSpilloverCache.getOnHeapCache().keys().forEach(onHeapKeys::add); + tieredSpilloverCache.getOnDiskCache().get().keys().forEach(diskTierKeys::add); assertEquals(tieredSpilloverCache.getOnHeapCache().count(), onHeapKeys.size()); assertEquals(tieredSpilloverCache.getOnDiskCache().get().count(), diskTierKeys.size()); @@ -312,7 +312,7 @@ public String load(String key) { // Verify that new items are part of onHeap cache. List actualOnHeapCacheKeys = new ArrayList<>(); - tieredSpilloverCache.cacheKeys(CacheStoreType.ON_HEAP).forEach(actualOnHeapCacheKeys::add); + tieredSpilloverCache.getOnHeapCache().keys().forEach(actualOnHeapCacheKeys::add); assertEquals(newKeyList.size(), actualOnHeapCacheKeys.size()); for (int i = 0; i < actualOnHeapCacheKeys.size(); i++) { @@ -387,8 +387,8 @@ public void testCacheKeys() throws Exception { List actualOnHeapKeys = new ArrayList<>(); List actualOnDiskKeys = new ArrayList<>(); - Iterable onHeapiterable = tieredSpilloverCache.cacheKeys(CacheStoreType.ON_HEAP); - Iterable onDiskiterable = tieredSpilloverCache.cacheKeys(CacheStoreType.DISK); + Iterable onHeapiterable = tieredSpilloverCache.getOnHeapCache().keys(); + Iterable onDiskiterable = tieredSpilloverCache.getOnDiskCache().get().keys(); onHeapiterable.iterator().forEachRemaining(actualOnHeapKeys::add); onDiskiterable.iterator().forEachRemaining(actualOnDiskKeys::add); for (String onHeapKey : onHeapKeys) { @@ -424,8 +424,6 @@ public void testRefresh() { eventListener, 0 ); - tieredSpilloverCache.refresh(CacheStoreType.ON_HEAP); - tieredSpilloverCache.refresh(CacheStoreType.DISK); tieredSpilloverCache.refresh(); }