Skip to content

Commit 250d325

Browse files
authored
Add hits and misses timing stats to DLS cache (elastic#133314)
- Add `hits_time_in_millis` and `misses_time_in_millis` to DLS cache stats - Approach is the same as GeoIpCache or EnrichCache
1 parent f1778c3 commit 250d325

File tree

5 files changed

+45
-4
lines changed

5 files changed

+45
-4
lines changed

docs/changelog/133314.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 133314
2+
summary: Add hits and misses timing stats to DLS cache
3+
area: Authorization
4+
type: enhancement
5+
issues: []

modules/ingest-geoip/src/main/java/org/elasticsearch/ingest/geoip/GeoIpCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ public String toString() {
4242
}
4343
};
4444

45-
private final LongSupplier relativeNanoTimeProvider;
4645
private final Cache<CacheKey, Object> cache;
46+
private final LongSupplier relativeNanoTimeProvider;
4747
private final LongAdder hitsTimeInNanos = new LongAdder();
4848
private final LongAdder missesTimeInNanos = new LongAdder();
4949

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCache.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252
import java.util.concurrent.ExecutorService;
5353
import java.util.concurrent.TimeUnit;
5454
import java.util.concurrent.atomic.AtomicLong;
55+
import java.util.concurrent.atomic.LongAdder;
5556
import java.util.concurrent.locks.ReentrantReadWriteLock;
57+
import java.util.function.LongSupplier;
5658

5759
/**
5860
* This is a cache for {@link BitSet} instances that are used with the {@link DocumentSubsetReader}.
@@ -122,18 +124,27 @@ public final class DocumentSubsetBitsetCache implements IndexReader.ClosedListen
122124
private final Cache<BitsetCacheKey, BitSet> bitsetCache;
123125
private final Map<IndexReader.CacheKey, Set<BitsetCacheKey>> keysByIndex;
124126
private final AtomicLong cacheFullWarningTime;
127+
private final LongSupplier relativeNanoTimeProvider;
128+
private final LongAdder hitsTimeInNanos = new LongAdder();
129+
private final LongAdder missesTimeInNanos = new LongAdder();
125130

126131
public DocumentSubsetBitsetCache(Settings settings, ThreadPool threadPool) {
127132
this(settings, threadPool.executor(ThreadPool.Names.GENERIC));
128133
}
129134

135+
// visible for testing
136+
DocumentSubsetBitsetCache(Settings settings, ExecutorService cleanupExecutor) {
137+
this(settings, cleanupExecutor, System::nanoTime);
138+
}
139+
130140
/**
131141
* @param settings The global settings object for this node
132142
* @param cleanupExecutor An executor on which the cache cleanup tasks can be run. Due to the way the cache is structured internally,
133143
* it is sometimes necessary to run an asynchronous task to synchronize the internal state.
144+
* @param relativeNanoTimeProvider Provider of nanos for code that needs to measure relative time.
134145
*/
135146
// visible for testing
136-
DocumentSubsetBitsetCache(Settings settings, ExecutorService cleanupExecutor) {
147+
DocumentSubsetBitsetCache(Settings settings, ExecutorService cleanupExecutor, LongSupplier relativeNanoTimeProvider) {
137148
final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
138149
this.cacheEvictionLock = new ReleasableLock(readWriteLock.writeLock());
139150
this.cacheModificationLock = new ReleasableLock(readWriteLock.readLock());
@@ -150,6 +161,7 @@ public DocumentSubsetBitsetCache(Settings settings, ThreadPool threadPool) {
150161

151162
this.keysByIndex = new ConcurrentHashMap<>();
152163
this.cacheFullWarningTime = new AtomicLong(0);
164+
this.relativeNanoTimeProvider = Objects.requireNonNull(relativeNanoTimeProvider);
153165
}
154166

155167
@Override
@@ -222,6 +234,8 @@ public long ramBytesUsed() {
222234
*/
223235
@Nullable
224236
public BitSet getBitSet(final Query query, final LeafReaderContext context) throws ExecutionException {
237+
final long cacheStart = relativeNanoTimeProvider.getAsLong();
238+
225239
final IndexReader.CacheHelper coreCacheHelper = context.reader().getCoreCacheHelper();
226240
if (coreCacheHelper == null) {
227241
try {
@@ -235,7 +249,9 @@ public BitSet getBitSet(final Query query, final LeafReaderContext context) thro
235249
final BitsetCacheKey cacheKey = new BitsetCacheKey(indexKey, query);
236250

237251
try (ReleasableLock ignored = cacheModificationLock.acquire()) {
252+
final boolean[] cacheKeyWasPresent = new boolean[] { true };
238253
final BitSet bitSet = bitsetCache.computeIfAbsent(cacheKey, ignore1 -> {
254+
cacheKeyWasPresent[0] = false;
239255
// This ensures all insertions into the set are guarded by ConcurrentHashMap's atomicity guarantees.
240256
keysByIndex.compute(indexKey, (ignore2, set) -> {
241257
if (set == null) {
@@ -264,6 +280,11 @@ public BitSet getBitSet(final Query query, final LeafReaderContext context) thro
264280
}
265281
return result;
266282
});
283+
if (cacheKeyWasPresent[0]) {
284+
hitsTimeInNanos.add(relativeNanoTimeProvider.getAsLong() - cacheStart);
285+
} else {
286+
missesTimeInNanos.add(relativeNanoTimeProvider.getAsLong() - cacheStart);
287+
}
267288
if (bitSet == NULL_MARKER) {
268289
return null;
269290
} else {
@@ -331,6 +352,8 @@ public Map<String, Object> usageStats() {
331352
stats.put("hits", cacheStats.getHits());
332353
stats.put("misses", cacheStats.getMisses());
333354
stats.put("evictions", cacheStats.getEvictions());
355+
stats.put("hits_time_in_millis", TimeValue.nsecToMSec(hitsTimeInNanos.sum()));
356+
stats.put("misses_time_in_millis", TimeValue.nsecToMSec(missesTimeInNanos.sum()));
334357
return Collections.unmodifiableMap(stats);
335358
}
336359

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCacheTests.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@
6262
import java.util.concurrent.Executors;
6363
import java.util.concurrent.TimeUnit;
6464
import java.util.concurrent.atomic.AtomicInteger;
65+
import java.util.concurrent.atomic.AtomicLong;
6566
import java.util.concurrent.atomic.AtomicReference;
67+
import java.util.function.LongSupplier;
6668
import java.util.function.Supplier;
6769

6870
import static org.hamcrest.Matchers.equalTo;
@@ -536,6 +538,8 @@ public void testHitsMissesAndEvictionsStats() throws Exception {
536538
stats.put("hits", 0L);
537539
stats.put("misses", 0L);
538540
stats.put("evictions", 0L);
541+
stats.put("hits_time_in_millis", 0L);
542+
stats.put("misses_time_in_millis", 0L);
539543
return stats;
540544
};
541545

@@ -551,12 +555,14 @@ public void testHitsMissesAndEvictionsStats() throws Exception {
551555
expectedStats.put("misses", 1L);
552556
expectedStats.put("memory", EXPECTED_BYTES_PER_BIT_SET + "b");
553557
expectedStats.put("memory_in_bytes", EXPECTED_BYTES_PER_BIT_SET);
558+
expectedStats.put("misses_time_in_millis", 1L);
554559
assertThat(cache.usageStats(), equalTo(expectedStats));
555560

556561
// second same lookup - hit
557562
final BitSet bitSet1Again = cache.getBitSet(query1, leafContext);
558563
assertThat(bitSet1Again, sameInstance(bitSet1));
559564
expectedStats.put("hits", 1L);
565+
expectedStats.put("hits_time_in_millis", 1L);
560566
assertThat(cache.usageStats(), equalTo(expectedStats));
561567

562568
// second query - miss, should evict the first one
@@ -568,13 +574,18 @@ public void testHitsMissesAndEvictionsStats() throws Exception {
568574
// see https://github.com/elastic/elasticsearch/issues/132842
569575
expectedStats.put("misses", 3L);
570576
expectedStats.put("evictions", 1L);
577+
// underlying Cache class tracks hits/misses, but timing is in DLS cache, which is why we have `2L` here,
578+
// because DLS cache is only hit once
579+
expectedStats.put("misses_time_in_millis", 2L);
571580
assertBusy(() -> { assertThat(cache.usageStats(), equalTo(expectedStats)); }, 200, TimeUnit.MILLISECONDS);
572581
});
573582

574583
final Map<String, Object> finalStats = emptyStatsSupplier.get();
575584
finalStats.put("hits", 1L);
576585
finalStats.put("misses", 3L);
577586
finalStats.put("evictions", 2L);
587+
finalStats.put("hits_time_in_millis", 1L);
588+
finalStats.put("misses_time_in_millis", 2L);
578589
assertThat(cache.usageStats(), equalTo(finalStats));
579590
}
580591

@@ -697,6 +708,8 @@ private void runTestOnIndices(int numberIndices, CheckedConsumer<List<TestIndexC
697708
}
698709

699710
private DocumentSubsetBitsetCache newCache(Settings settings) {
700-
return new DocumentSubsetBitsetCache(settings, singleThreadExecutor);
711+
final AtomicLong increasingMillisTime = new AtomicLong();
712+
final LongSupplier relativeNanoTimeProvider = () -> TimeUnit.MILLISECONDS.toNanos(increasingMillisTime.getAndIncrement());
713+
return new DocumentSubsetBitsetCache(settings, singleThreadExecutor, relativeNanoTimeProvider);
701714
}
702715
}

x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ public final class EnrichCache {
4646
private static final CacheValue EMPTY_CACHE_VALUE = new CacheValue(List.of(), CacheKey.CACHE_KEY_SIZE);
4747

4848
private final Cache<CacheKey, CacheValue> cache;
49+
private final LongAdder sizeInBytes = new LongAdder();
4950
private final LongSupplier relativeNanoTimeProvider;
5051
private final LongAdder hitsTimeInNanos = new LongAdder();
5152
private final LongAdder missesTimeInNanos = new LongAdder();
52-
private final LongAdder sizeInBytes = new LongAdder();
5353

5454
EnrichCache(long maxSize) {
5555
this(maxSize, System::nanoTime);

0 commit comments

Comments
 (0)