Skip to content

Commit a5c5675

Browse files
peteralfonsiPeter Alfonsi
andcommitted
[Tiered Caching] Adds stats implementation for TieredSpilloverCache (opensearch-project#13236)
Stats rework part 4 of 4 --------- Signed-off-by: Peter Alfonsi <[email protected]> Co-authored-by: Peter Alfonsi <[email protected]>
1 parent 46944cb commit a5c5675

File tree

12 files changed

+754
-100
lines changed

12 files changed

+754
-100
lines changed

modules/cache-common/src/internalClusterTest/java/org.opensearch.cache.common.tier/TieredSpilloverCacheIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ public MockDiskCachePlugin() {}
550550

551551
@Override
552552
public Map<String, ICache.Factory> getCacheFactoryMap() {
553-
return Map.of(MockDiskCache.MockDiskCacheFactory.NAME, new MockDiskCache.MockDiskCacheFactory(0, 1000));
553+
return Map.of(MockDiskCache.MockDiskCacheFactory.NAME, new MockDiskCache.MockDiskCacheFactory(0, 1000, false));
554554
}
555555

556556
@Override

modules/cache-common/src/main/java/org/opensearch/cache/common/tier/TieredSpilloverCache.java

Lines changed: 203 additions & 47 deletions
Large diffs are not rendered by default.
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.cache.common.tier;
10+
11+
import org.opensearch.common.cache.stats.DefaultCacheStatsHolder;
12+
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import java.util.function.Consumer;
16+
17+
/**
18+
* A tier-aware version of DefaultCacheStatsHolder. Overrides the incrementer functions, as we can't just add the on-heap
19+
* and disk stats to get a total for the cache as a whole. If the disk tier is present, the total hits, size, and entries
20+
* should be the sum of both tiers' values, but the total misses and evictions should be the disk tier's values.
21+
* When the disk tier isn't present, on-heap misses and evictions should contribute to the total.
22+
*
23+
* For example, if the heap tier has 5 misses and the disk tier has 4, the total cache has had 4 misses, not 9.
24+
* The same goes for evictions. Other stats values add normally.
25+
*
26+
* This means for misses and evictions, if we are incrementing for the on-heap tier and the disk tier is present,
27+
* we have to increment only the leaf nodes corresponding to the on-heap tier itself, and not its ancestors,
28+
* which correspond to totals including both tiers. If the disk tier is not present, we do increment the ancestor nodes.
29+
*/
30+
public class TieredSpilloverCacheStatsHolder extends DefaultCacheStatsHolder {
31+
32+
/** Whether the disk cache is currently enabled. */
33+
private boolean diskCacheEnabled;
34+
35+
// Common values used for tier dimension
36+
37+
/** The name for the tier dimension. */
38+
public static final String TIER_DIMENSION_NAME = "tier";
39+
40+
/** Dimension value for on-heap cache, like OpenSearchOnHeapCache.*/
41+
public static final String TIER_DIMENSION_VALUE_ON_HEAP = "on_heap";
42+
43+
/** Dimension value for on-disk cache, like EhcacheDiskCache. */
44+
public static final String TIER_DIMENSION_VALUE_DISK = "disk";
45+
46+
/**
47+
* Constructor for the stats holder.
48+
* @param originalDimensionNames the original dimension names, not including TIER_DIMENSION_NAME
49+
* @param diskCacheEnabled whether the disk tier starts out enabled
50+
*/
51+
public TieredSpilloverCacheStatsHolder(List<String> originalDimensionNames, boolean diskCacheEnabled) {
52+
super(
53+
getDimensionNamesWithTier(originalDimensionNames),
54+
TieredSpilloverCache.TieredSpilloverCacheFactory.TIERED_SPILLOVER_CACHE_NAME
55+
);
56+
this.diskCacheEnabled = diskCacheEnabled;
57+
}
58+
59+
private static List<String> getDimensionNamesWithTier(List<String> dimensionNames) {
60+
List<String> dimensionNamesWithTier = new ArrayList<>(dimensionNames);
61+
dimensionNamesWithTier.add(TIER_DIMENSION_NAME);
62+
return dimensionNamesWithTier;
63+
}
64+
65+
/**
66+
* Add tierValue to the end of a copy of the initial dimension values, so they can appropriately be used in this stats holder.
67+
*/
68+
List<String> getDimensionsWithTierValue(List<String> initialDimensions, String tierValue) {
69+
List<String> result = new ArrayList<>(initialDimensions);
70+
result.add(tierValue);
71+
return result;
72+
}
73+
74+
private String validateTierDimensionValue(List<String> dimensionValues) {
75+
String tierDimensionValue = dimensionValues.get(dimensionValues.size() - 1);
76+
assert tierDimensionValue.equals(TIER_DIMENSION_VALUE_ON_HEAP) || tierDimensionValue.equals(TIER_DIMENSION_VALUE_DISK)
77+
: "Invalid tier dimension value";
78+
return tierDimensionValue;
79+
}
80+
81+
@Override
82+
public void incrementHits(List<String> dimensionValues) {
83+
validateTierDimensionValue(dimensionValues);
84+
// Hits from either tier should be included in the total values.
85+
super.incrementHits(dimensionValues);
86+
}
87+
88+
@Override
89+
public void incrementMisses(List<String> dimensionValues) {
90+
final String tierValue = validateTierDimensionValue(dimensionValues);
91+
92+
// If the disk tier is present, only misses from the disk tier should be included in total values.
93+
Consumer<Node> missIncrementer = (node) -> {
94+
if (tierValue.equals(TIER_DIMENSION_VALUE_ON_HEAP) && diskCacheEnabled) {
95+
// If on-heap tier, increment only the leaf node corresponding to the on heap values; not the total values in its parent
96+
// nodes
97+
if (node.isAtLowestLevel()) {
98+
node.incrementMisses();
99+
}
100+
} else {
101+
// If disk tier, or on-heap tier with a disabled disk tier, increment the leaf node and its parents
102+
node.incrementMisses();
103+
}
104+
};
105+
internalIncrement(dimensionValues, missIncrementer, true);
106+
}
107+
108+
@Override
109+
public void incrementEvictions(List<String> dimensionValues) {
110+
final String tierValue = validateTierDimensionValue(dimensionValues);
111+
112+
// If the disk tier is present, only evictions from the disk tier should be included in total values.
113+
Consumer<DefaultCacheStatsHolder.Node> evictionsIncrementer = (node) -> {
114+
if (tierValue.equals(TIER_DIMENSION_VALUE_ON_HEAP) && diskCacheEnabled) {
115+
// If on-heap tier, increment only the leaf node corresponding to the on heap values; not the total values in its parent
116+
// nodes
117+
if (node.isAtLowestLevel()) {
118+
node.incrementEvictions();
119+
}
120+
} else {
121+
// If disk tier, or on-heap tier with a disabled disk tier, increment the leaf node and its parents
122+
node.incrementEvictions();
123+
}
124+
};
125+
internalIncrement(dimensionValues, evictionsIncrementer, true);
126+
}
127+
128+
@Override
129+
public void incrementSizeInBytes(List<String> dimensionValues, long amountBytes) {
130+
validateTierDimensionValue(dimensionValues);
131+
// Size from either tier should be included in the total values.
132+
super.incrementSizeInBytes(dimensionValues, amountBytes);
133+
}
134+
135+
// For decrements, we should not create nodes if they are absent. This protects us from erroneously decrementing values for keys
136+
// which have been entirely deleted, for example in an async removal listener.
137+
@Override
138+
public void decrementSizeInBytes(List<String> dimensionValues, long amountBytes) {
139+
validateTierDimensionValue(dimensionValues);
140+
// Size from either tier should be included in the total values.
141+
super.decrementSizeInBytes(dimensionValues, amountBytes);
142+
}
143+
144+
@Override
145+
public void incrementItems(List<String> dimensionValues) {
146+
validateTierDimensionValue(dimensionValues);
147+
// Entries from either tier should be included in the total values.
148+
super.incrementItems(dimensionValues);
149+
}
150+
151+
@Override
152+
public void decrementItems(List<String> dimensionValues) {
153+
validateTierDimensionValue(dimensionValues);
154+
// Entries from either tier should be included in the total values.
155+
super.decrementItems(dimensionValues);
156+
}
157+
158+
void setDiskCacheEnabled(boolean diskCacheEnabled) {
159+
this.diskCacheEnabled = diskCacheEnabled;
160+
}
161+
}

modules/cache-common/src/test/java/org/opensearch/cache/common/tier/MockDiskCache.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
import org.opensearch.common.cache.RemovalNotification;
1717
import org.opensearch.common.cache.RemovalReason;
1818
import org.opensearch.common.cache.serializer.Serializer;
19+
import org.opensearch.common.cache.stats.CacheStatsHolder;
20+
import org.opensearch.common.cache.stats.DefaultCacheStatsHolder;
1921
import org.opensearch.common.cache.stats.ImmutableCacheStatsHolder;
22+
import org.opensearch.common.cache.stats.NoopCacheStatsHolder;
2023
import org.opensearch.common.cache.store.builders.ICacheBuilder;
2124
import org.opensearch.common.cache.store.config.CacheConfig;
2225

2326
import java.util.Iterator;
27+
import java.util.List;
2428
import java.util.Map;
2529
import java.util.NoSuchElementException;
2630
import java.util.concurrent.ConcurrentHashMap;
@@ -32,12 +36,19 @@ public class MockDiskCache<K, V> implements ICache<K, V> {
3236
long delay;
3337

3438
private final RemovalListener<ICacheKey<K>, V> removalListener;
39+
private final CacheStatsHolder statsHolder; // Only update for number of entries; this is only used to test statsTrackingEnabled logic
40+
// in TSC
3541

36-
public MockDiskCache(int maxSize, long delay, RemovalListener<ICacheKey<K>, V> removalListener) {
42+
public MockDiskCache(int maxSize, long delay, RemovalListener<ICacheKey<K>, V> removalListener, boolean statsTrackingEnabled) {
3743
this.maxSize = maxSize;
3844
this.delay = delay;
3945
this.removalListener = removalListener;
4046
this.cache = new ConcurrentHashMap<ICacheKey<K>, V>();
47+
if (statsTrackingEnabled) {
48+
this.statsHolder = new DefaultCacheStatsHolder(List.of(), "mock_disk_cache");
49+
} else {
50+
this.statsHolder = NoopCacheStatsHolder.getInstance();
51+
}
4152
}
4253

4354
@Override
@@ -50,13 +61,15 @@ public V get(ICacheKey<K> key) {
5061
public void put(ICacheKey<K> key, V value) {
5162
if (this.cache.size() >= maxSize) { // For simplification
5263
this.removalListener.onRemoval(new RemovalNotification<>(key, value, RemovalReason.EVICTED));
64+
this.statsHolder.decrementItems(List.of());
5365
}
5466
try {
5567
Thread.sleep(delay);
5668
} catch (InterruptedException e) {
5769
throw new RuntimeException(e);
5870
}
5971
this.cache.put(key, value);
72+
this.statsHolder.incrementItems(List.of());
6073
}
6174

6275
@Override
@@ -73,6 +86,7 @@ public V computeIfAbsent(ICacheKey<K> key, LoadAwareCacheLoader<ICacheKey<K>, V>
7386

7487
@Override
7588
public void invalidate(ICacheKey<K> key) {
89+
removalListener.onRemoval(new RemovalNotification<>(key, cache.get(key), RemovalReason.INVALIDATED));
7690
this.cache.remove(key);
7791
}
7892

@@ -96,7 +110,9 @@ public void refresh() {}
96110

97111
@Override
98112
public ImmutableCacheStatsHolder stats() {
99-
return null;
113+
// To allow testing of statsTrackingEnabled logic in TSC, return a dummy ImmutableCacheStatsHolder with the
114+
// right number of entries, unless statsTrackingEnabled is false
115+
return statsHolder.getImmutableCacheStatsHolder(null);
100116
}
101117

102118
@Override
@@ -114,10 +130,12 @@ public static class MockDiskCacheFactory implements Factory {
114130
public static final String NAME = "mockDiskCache";
115131
final long delay;
116132
final int maxSize;
133+
final boolean statsTrackingEnabled;
117134

118-
public MockDiskCacheFactory(long delay, int maxSize) {
135+
public MockDiskCacheFactory(long delay, int maxSize, boolean statsTrackingEnabled) {
119136
this.delay = delay;
120137
this.maxSize = maxSize;
138+
this.statsTrackingEnabled = statsTrackingEnabled;
121139
}
122140

123141
@Override
@@ -128,6 +146,7 @@ public <K, V> ICache<K, V> create(CacheConfig<K, V> config, CacheType cacheType,
128146
.setMaxSize(maxSize)
129147
.setDeliberateDelay(delay)
130148
.setRemovalListener(config.getRemovalListener())
149+
.setStatsTrackingEnabled(config.getStatsTrackingEnabled())
131150
.build();
132151
}
133152

@@ -146,7 +165,7 @@ public static class Builder<K, V> extends ICacheBuilder<K, V> {
146165

147166
@Override
148167
public ICache<K, V> build() {
149-
return new MockDiskCache<K, V>(this.maxSize, this.delay, this.getRemovalListener());
168+
return new MockDiskCache<K, V>(this.maxSize, this.delay, this.getRemovalListener(), getStatsTrackingEnabled());
150169
}
151170

152171
public Builder<K, V> setMaxSize(int maxSize) {

0 commit comments

Comments
 (0)