Skip to content

Commit f84d28d

Browse files
peteralfonsiPeter Alfonsi
and
Peter Alfonsi
authored
[Tiered Caching] Gate CacheStatsHolder logic behind FeatureFlags.PLUGGABLE_CACHE setting (opensearch-project#13238)
Stats rework step 2 of 4 --------- Signed-off-by: Peter Alfonsi <petealft@amazon.com> Co-authored-by: Peter Alfonsi <petealft@amazon.com>
1 parent 207bbad commit f84d28d

File tree

11 files changed

+487
-314
lines changed

11 files changed

+487
-314
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2424
- Add an individual setting of rate limiter for segment replication ([#12959](https://github.com/opensearch-project/OpenSearch/pull/12959))
2525
- [Streaming Indexing] Ensure support of the new transport by security plugin ([#13174](https://github.com/opensearch-project/OpenSearch/pull/13174))
2626
- Add cluster setting to dynamically configure the buckets for filter rewrite optimization. ([#13179](https://github.com/opensearch-project/OpenSearch/pull/13179))
27+
- [Tiered Caching] Gate new stats logic behind FeatureFlags.PLUGGABLE_CACHE ([#13238](https://github.com/opensearch-project/OpenSearch/pull/13238))
2728
- [Tiered Caching] Add a dynamic setting to disable/enable disk cache. ([#13373](https://github.com/opensearch-project/OpenSearch/pull/13373))
2829
- [Remote Store] Add capability of doing refresh as determined by the translog ([#12992](https://github.com/opensearch-project/OpenSearch/pull/12992))
2930
- [Tiered caching] Make Indices Request Cache Stale Key Mgmt Threshold setting dynamic ([#12941](https://github.com/opensearch-project/OpenSearch/pull/12941))

plugins/cache-ehcache/src/main/java/org/opensearch/cache/store/disk/EhcacheDiskCache.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.opensearch.common.cache.serializer.ICacheKeySerializer;
2626
import org.opensearch.common.cache.serializer.Serializer;
2727
import org.opensearch.common.cache.stats.CacheStatsHolder;
28+
import org.opensearch.common.cache.stats.DefaultCacheStatsHolder;
2829
import org.opensearch.common.cache.stats.ImmutableCacheStatsHolder;
2930
import org.opensearch.common.cache.store.builders.ICacheBuilder;
3031
import org.opensearch.common.cache.store.config.CacheConfig;
@@ -162,7 +163,8 @@ private EhcacheDiskCache(Builder<K, V> builder) {
162163
this.ehCacheEventListener = new EhCacheEventListener(builder.getRemovalListener(), builder.getWeigher());
163164
this.cache = buildCache(Duration.ofMillis(expireAfterAccess.getMillis()), builder);
164165
List<String> dimensionNames = Objects.requireNonNull(builder.dimensionNames, "Dimension names can't be null");
165-
this.cacheStatsHolder = new CacheStatsHolder(dimensionNames);
166+
// If this cache is being used, FeatureFlags.PLUGGABLE_CACHE is already on, so we can always use the DefaultCacheStatsHolder.
167+
this.cacheStatsHolder = new DefaultCacheStatsHolder(dimensionNames);
166168
}
167169

168170
@SuppressWarnings({ "rawtypes" })

server/src/main/java/org/opensearch/common/cache/stats/CacheStatsHolder.java

+13-270
Original file line numberDiff line numberDiff line change
@@ -8,288 +8,31 @@
88

99
package org.opensearch.common.cache.stats;
1010

11-
import java.util.Collections;
12-
import java.util.HashMap;
1311
import java.util.List;
14-
import java.util.Map;
15-
import java.util.TreeMap;
16-
import java.util.concurrent.ConcurrentHashMap;
17-
import java.util.concurrent.locks.Lock;
18-
import java.util.concurrent.locks.ReentrantLock;
19-
import java.util.function.Consumer;
2012

2113
/**
22-
* A class ICache implementations use to internally keep track of their stats across multiple dimensions.
23-
* Not intended to be exposed outside the cache; for this, caches use getImmutableCacheStatsHolder() to create an immutable
24-
* copy of the current state of the stats.
25-
* Currently, in the IRC, the stats tracked in a CacheStatsHolder will not appear for empty shards that have had no cache
26-
* operations done on them yet. This might be changed in the future, by exposing a method to add empty nodes to the
27-
* tree in CacheStatsHolder in the ICache interface.
28-
*
29-
* @opensearch.experimental
14+
* An interface extended by DefaultCacheStatsHolder and NoopCacheStatsHolder.
3015
*/
31-
public class CacheStatsHolder {
32-
33-
// The list of permitted dimensions. Should be ordered from "outermost" to "innermost", as you would like to
34-
// aggregate them in an API response.
35-
private final List<String> dimensionNames;
36-
// A tree structure based on dimension values, which stores stats values in its leaf nodes.
37-
// Non-leaf nodes have stats matching the sum of their children.
38-
// We use a tree structure, rather than a map with concatenated keys, to save on memory usage. If there are many leaf
39-
// nodes that share a parent, that parent's dimension value will only be stored once, not many times.
40-
private final Node statsRoot;
41-
// To avoid sync problems, obtain a lock before creating or removing nodes in the stats tree.
42-
// No lock is needed to edit stats on existing nodes.
43-
private final Lock lock = new ReentrantLock();
44-
45-
public CacheStatsHolder(List<String> dimensionNames) {
46-
this.dimensionNames = Collections.unmodifiableList(dimensionNames);
47-
this.statsRoot = new Node("", true); // The root node has the empty string as its dimension value
48-
}
49-
50-
public List<String> getDimensionNames() {
51-
return dimensionNames;
52-
}
53-
54-
// For all these increment functions, the dimensions list comes from the key, and contains all dimensions present in dimensionNames.
55-
// The order has to match the order given in dimensionNames.
56-
public void incrementHits(List<String> dimensionValues) {
57-
internalIncrement(dimensionValues, Node::incrementHits, true);
58-
}
59-
60-
public void incrementMisses(List<String> dimensionValues) {
61-
internalIncrement(dimensionValues, Node::incrementMisses, true);
62-
}
63-
64-
public void incrementEvictions(List<String> dimensionValues) {
65-
internalIncrement(dimensionValues, Node::incrementEvictions, true);
66-
}
67-
68-
public void incrementSizeInBytes(List<String> dimensionValues, long amountBytes) {
69-
internalIncrement(dimensionValues, (node) -> node.incrementSizeInBytes(amountBytes), true);
70-
}
71-
72-
// For decrements, we should not create nodes if they are absent. This protects us from erroneously decrementing values for keys
73-
// which have been entirely deleted, for example in an async removal listener.
74-
public void decrementSizeInBytes(List<String> dimensionValues, long amountBytes) {
75-
internalIncrement(dimensionValues, (node) -> node.decrementSizeInBytes(amountBytes), false);
76-
}
77-
78-
public void incrementEntries(List<String> dimensionValues) {
79-
internalIncrement(dimensionValues, Node::incrementEntries, true);
80-
}
81-
82-
public void decrementEntries(List<String> dimensionValues) {
83-
internalIncrement(dimensionValues, Node::decrementEntries, false);
84-
}
85-
86-
/**
87-
* Reset number of entries and memory size when all keys leave the cache, but don't reset hit/miss/eviction numbers.
88-
* This is in line with the behavior of the existing API when caches are cleared.
89-
*/
90-
public void reset() {
91-
resetHelper(statsRoot);
92-
}
93-
94-
private void resetHelper(Node current) {
95-
current.resetSizeAndEntries();
96-
for (Node child : current.children.values()) {
97-
resetHelper(child);
98-
}
99-
}
100-
101-
public long count() {
102-
// Include this here so caches don't have to create an entire CacheStats object to run count().
103-
return statsRoot.getEntries();
104-
}
105-
106-
private void internalIncrement(List<String> dimensionValues, Consumer<Node> adder, boolean createNodesIfAbsent) {
107-
assert dimensionValues.size() == dimensionNames.size();
108-
// First try to increment without creating nodes
109-
boolean didIncrement = internalIncrementHelper(dimensionValues, statsRoot, 0, adder, false);
110-
// If we failed to increment, because nodes had to be created, obtain the lock and run again while creating nodes if needed
111-
if (!didIncrement && createNodesIfAbsent) {
112-
try {
113-
lock.lock();
114-
internalIncrementHelper(dimensionValues, statsRoot, 0, adder, true);
115-
} finally {
116-
lock.unlock();
117-
}
118-
}
119-
}
120-
121-
/**
122-
* Use the incrementer function to increment/decrement a value in the stats for a set of dimensions.
123-
* If createNodesIfAbsent is true, and there is no stats for this set of dimensions, create one.
124-
* Returns true if the increment was applied, false if not.
125-
*/
126-
private boolean internalIncrementHelper(
127-
List<String> dimensionValues,
128-
Node node,
129-
int depth, // Pass in the depth to avoid having to slice the list for each node.
130-
Consumer<Node> adder,
131-
boolean createNodesIfAbsent
132-
) {
133-
if (depth == dimensionValues.size()) {
134-
// This is the leaf node we are trying to reach
135-
adder.accept(node);
136-
return true;
137-
}
138-
139-
Node child = node.getChild(dimensionValues.get(depth));
140-
if (child == null) {
141-
if (createNodesIfAbsent) {
142-
boolean createMapInChild = depth < dimensionValues.size() - 1;
143-
child = node.createChild(dimensionValues.get(depth), createMapInChild);
144-
} else {
145-
return false;
146-
}
147-
}
148-
if (internalIncrementHelper(dimensionValues, child, depth + 1, adder, createNodesIfAbsent)) {
149-
// Function returns true if the next node down was incremented
150-
adder.accept(node);
151-
return true;
152-
}
153-
return false;
154-
}
155-
156-
/**
157-
* Produce an immutable version of these stats.
158-
*/
159-
public ImmutableCacheStatsHolder getImmutableCacheStatsHolder() {
160-
return new ImmutableCacheStatsHolder(statsRoot.snapshot(), dimensionNames);
161-
}
162-
163-
public void removeDimensions(List<String> dimensionValues) {
164-
assert dimensionValues.size() == dimensionNames.size() : "Must specify a value for every dimension when removing from StatsHolder";
165-
// As we are removing nodes from the tree, obtain the lock
166-
lock.lock();
167-
try {
168-
removeDimensionsHelper(dimensionValues, statsRoot, 0);
169-
} finally {
170-
lock.unlock();
171-
}
172-
}
173-
174-
// Returns a CacheStatsCounterSnapshot object for the stats to decrement if the removal happened, null otherwise.
175-
private ImmutableCacheStats removeDimensionsHelper(List<String> dimensionValues, Node node, int depth) {
176-
if (depth == dimensionValues.size()) {
177-
// Pass up a snapshot of the original stats to avoid issues when the original is decremented by other fn invocations
178-
return node.getImmutableStats();
179-
}
180-
Node child = node.getChild(dimensionValues.get(depth));
181-
if (child == null) {
182-
return null;
183-
}
184-
ImmutableCacheStats statsToDecrement = removeDimensionsHelper(dimensionValues, child, depth + 1);
185-
if (statsToDecrement != null) {
186-
// The removal took place, decrement values and remove this node from its parent if it's now empty
187-
node.decrementBySnapshot(statsToDecrement);
188-
if (child.getChildren().isEmpty()) {
189-
node.children.remove(child.getDimensionValue());
190-
}
191-
}
192-
return statsToDecrement;
193-
}
194-
195-
// pkg-private for testing
196-
Node getStatsRoot() {
197-
return statsRoot;
198-
}
199-
200-
static class Node {
201-
private final String dimensionValue;
202-
// Map from dimensionValue to the DimensionNode for that dimension value.
203-
final Map<String, Node> children;
204-
// The stats for this node. If a leaf node, corresponds to the stats for this combination of dimensions; if not,
205-
// contains the sum of its children's stats.
206-
private CacheStats stats;
207-
208-
// Used for leaf nodes to avoid allocating many unnecessary maps
209-
private static final Map<String, Node> EMPTY_CHILDREN_MAP = new HashMap<>();
210-
211-
Node(String dimensionValue, boolean createChildrenMap) {
212-
this.dimensionValue = dimensionValue;
213-
if (createChildrenMap) {
214-
this.children = new ConcurrentHashMap<>();
215-
} else {
216-
this.children = EMPTY_CHILDREN_MAP;
217-
}
218-
this.stats = new CacheStats();
219-
}
220-
221-
public String getDimensionValue() {
222-
return dimensionValue;
223-
}
224-
225-
protected Map<String, Node> getChildren() {
226-
// We can safely iterate over ConcurrentHashMap without worrying about thread issues.
227-
return children;
228-
}
229-
230-
// Functions for modifying internal CacheStatsCounter without callers having to be aware of CacheStatsCounter
231-
232-
void incrementHits() {
233-
this.stats.incrementHits();
234-
}
235-
236-
void incrementMisses() {
237-
this.stats.incrementMisses();
238-
}
239-
240-
void incrementEvictions() {
241-
this.stats.incrementEvictions();
242-
}
243-
244-
void incrementSizeInBytes(long amountBytes) {
245-
this.stats.incrementSizeInBytes(amountBytes);
246-
}
16+
public interface CacheStatsHolder {
17+
void incrementHits(List<String> dimensionValues);
24718

248-
void decrementSizeInBytes(long amountBytes) {
249-
this.stats.decrementSizeInBytes(amountBytes);
250-
}
19+
void incrementMisses(List<String> dimensionValues);
25120

252-
void incrementEntries() {
253-
this.stats.incrementEntries();
254-
}
21+
void incrementEvictions(List<String> dimensionValues);
25522

256-
void decrementEntries() {
257-
this.stats.decrementEntries();
258-
}
23+
void incrementSizeInBytes(List<String> dimensionValues, long amountBytes);
25924

260-
long getEntries() {
261-
return this.stats.getEntries();
262-
}
25+
void decrementSizeInBytes(List<String> dimensionValues, long amountBytes);
26326

264-
ImmutableCacheStats getImmutableStats() {
265-
return this.stats.immutableSnapshot();
266-
}
27+
void incrementEntries(List<String> dimensionValues);
26728

268-
void decrementBySnapshot(ImmutableCacheStats snapshot) {
269-
this.stats.subtract(snapshot);
270-
}
29+
void decrementEntries(List<String> dimensionValues);
27130

272-
void resetSizeAndEntries() {
273-
this.stats.resetSizeAndEntries();
274-
}
31+
void reset();
27532

276-
Node getChild(String dimensionValue) {
277-
return children.get(dimensionValue);
278-
}
33+
long count();
27934

280-
Node createChild(String dimensionValue, boolean createMapInChild) {
281-
return children.computeIfAbsent(dimensionValue, (key) -> new Node(dimensionValue, createMapInChild));
282-
}
35+
void removeDimensions(List<String> dimensionValues);
28336

284-
ImmutableCacheStatsHolder.Node snapshot() {
285-
TreeMap<String, ImmutableCacheStatsHolder.Node> snapshotChildren = null;
286-
if (!children.isEmpty()) {
287-
snapshotChildren = new TreeMap<>();
288-
for (Node child : children.values()) {
289-
snapshotChildren.put(child.getDimensionValue(), child.snapshot());
290-
}
291-
}
292-
return new ImmutableCacheStatsHolder.Node(dimensionValue, snapshotChildren, getImmutableStats());
293-
}
294-
}
37+
ImmutableCacheStatsHolder getImmutableCacheStatsHolder();
29538
}

0 commit comments

Comments
 (0)