Author: mduerig
Date: Tue May 2 15:18:56 2017
New Revision: 1793528
URL: http://svn.apache.org/viewvc?rev=1793528&view=rev
Log:
OAK-5956: Improve cache statistics of the segment cache
Implement cache stats
Modified:
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java
Modified:
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java
(original)
+++
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentCache.java
Tue May 2 15:18:56 2017
@@ -21,43 +21,47 @@ package org.apache.jackrabbit.oak.segmen
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nonnull;
-import org.apache.jackrabbit.oak.cache.CacheStats;
-import org.apache.jackrabbit.oak.segment.CacheWeights.SegmentCacheWeigher;
-
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.cache.Weigher;
+import org.apache.jackrabbit.oak.cache.AbstractCacheStats;
+import org.apache.jackrabbit.oak.segment.CacheWeights.SegmentCacheWeigher;
/**
- * A cache for {@link Segment} instances by their {@link SegmentId}.
+ * A cache for {@link SegmentId#isDataSegmentId() data} {@link Segment}
instances by their
+ * {@link SegmentId}. This cache ignores {@link SegmentId#isBulkSegmentId()
bulk} segments.
* <p>
* Conceptually this cache serves as a 2nd level cache for segments. The 1st
level cache is
* implemented by memoising the segment in its id (see {@link
SegmentId#segment}. Every time
* an segment is evicted from this cache the memoised segment is discarded (see
- * {@link SegmentId#unloaded()}).
- * <p>
- * As a consequence this cache is actually only queried for segments it does
not contain,
- * which are then loaded through the loader passed to {@link
#getSegment(SegmentId, Callable)}.
- * This behaviour is eventually reflected in the cache statistics (see {@link
#getCacheStats()}),
- * which always reports a {@link CacheStats#getHitRate()} () miss rate} of 1.
+ * {@link SegmentId#unloaded()}) and {@link SegmentId#onAccess}.
*/
public class SegmentCache {
+ /** Default maximum weight of this cache in MB */
public static final int DEFAULT_SEGMENT_CACHE_MB = 256;
+ /** Weigher to determine the current weight of all items in this cache */
private final Weigher<SegmentId, Segment> weigher = new
SegmentCacheWeigher();
+ /** Maximum weight of the items in this cache */
private final long maximumWeight;
+ /** Cache of recently accessed segments */
+ @Nonnull
+ private final Cache<SegmentId, Segment> cache;
+
/**
- * Cache of recently accessed segments
+ * Statistics of this cache. Do to the special access patter (see class
comment), we cannot
+ * rely on {@link Cache#stats()}.
*/
@Nonnull
- private final Cache<SegmentId, Segment> cache;
+ private final Stats stats = new Stats("Segment Cache");
/**
* Create a new segment cache of the given size.
@@ -67,25 +71,43 @@ public class SegmentCache {
this.maximumWeight = cacheSizeMB * 1024 * 1024;
this.cache = CacheBuilder.newBuilder()
.concurrencyLevel(16)
- .recordStats()
.maximumWeight(maximumWeight)
.weigher(weigher)
- .removalListener(new RemovalListener<SegmentId, Segment>() {
- @Override
- public void onRemoval(@Nonnull
RemovalNotification<SegmentId, Segment> notification) {
- SegmentId id = notification.getKey();
- if (id != null) {
- id.unloaded();
- }
- }
- }).build();
+ .removalListener(this::onRemove)
+ .build();
+ }
+
+ /**
+ * Create a new segment cache with the {@link #DEFAULT_SEGMENT_CACHE_MB
default size}.
+ */
+ public SegmentCache() {
+ this(DEFAULT_SEGMENT_CACHE_MB);
+ }
+
+ /**
+ * Removal handler called whenever an item is evicted from the cache.
Propagates
+ * to {@link SegmentId#unloaded()}.
+ */
+ private void onRemove(@Nonnull RemovalNotification<SegmentId, Segment>
notification) {
+ SegmentId id = notification.getKey();
+ if (id != null) {
+ Segment segment = notification.getValue();
+ if (segment != null) {
+ stats.currentWeight.addAndGet(-weigher.weigh(id, segment));
+ }
+ stats.evictionCount.incrementAndGet();
+ id.unloaded();
+ }
}
- private void put(@Nonnull SegmentId id, Segment segment) {
+ /** Unconditionally put an item in the cache */
+ private Segment put(@Nonnull SegmentId id, @Nonnull Segment segment) {
// Call loaded *before* putting the segment into the cache as the
latter
// might cause it to get evicted right away again.
id.loaded(segment);
cache.put(id, segment);
+ stats.currentWeight.addAndGet(weigher.weigh(id, segment));
+ return segment;
}
/**
@@ -98,30 +120,40 @@ public class SegmentCache {
@Nonnull
public Segment getSegment(@Nonnull final SegmentId id, @Nonnull final
Callable<Segment> loader)
throws ExecutionException {
+ // Load bulk segment directly without putting it in cache
try {
- Segment segment = loader.call();
if (id.isBulkSegmentId()) {
- return segment;
+ return loader.call();
}
+ } catch (Exception e) {
+ throw new ExecutionException(e);
+ }
- put(id, segment);
- return segment;
+ // Load data segment and put it in the cache
+ try {
+ long t0 = System.nanoTime();
+ Segment segment = loader.call();
+ stats.loadSuccessCount.incrementAndGet();
+ stats.loadTime.addAndGet(System.nanoTime() - t0);
+ stats.missCount.incrementAndGet();
+
+ return put(id, segment);
} catch (Exception e) {
+ stats.loadExceptionCount.incrementAndGet();
throw new ExecutionException(e);
}
}
/**
- * Put a segment into the cache
+ * Put a segment into the cache. This method does nothing for
+ * {@link SegmentId#isBulkSegmentId() bulk} segments.
* @param segment the segment to cache
*/
public void putSegment(@Nonnull Segment segment) {
- SegmentId segmentId = segment.getSegmentId();
- if (segmentId.isBulkSegmentId()) {
- return;
+ SegmentId id = segment.getSegmentId();
+ if (!id.isBulkSegmentId()) {
+ put(id, segment);
}
-
- put(segmentId, segment);
}
/**
@@ -132,11 +164,75 @@ public class SegmentCache {
}
/**
- * See the class comment regarding some peculiarities of this cache's
statistics
- * @return statistics for this cache.
+ * @return Statistics for this cache.
*/
@Nonnull
- public CacheStats getCacheStats() {
- return new CacheStats(cache, "Segment Cache", weigher, maximumWeight);
+ public AbstractCacheStats getCacheStats() {
+ return stats;
+ }
+
+ /**
+ * Record a hit in this cache's underlying statistics.
+ * @see SegmentId#onAccess
+ */
+ public void recordHit() {
+ stats.hitCount.incrementAndGet();
+ }
+
+ /** We cannot rely on the statistics of the underlying Guava cache as all
cache hits
+ * are taken by {@link SegmentId#getSegment()} and thus never seen by the
cache.
+ */
+ private class Stats extends AbstractCacheStats {
+ @Nonnull
+ final AtomicLong currentWeight = new AtomicLong();
+
+ @Nonnull
+ final AtomicLong loadSuccessCount = new AtomicLong();
+
+ @Nonnull
+ final AtomicInteger loadExceptionCount = new AtomicInteger();
+
+ @Nonnull
+ final AtomicLong loadTime = new AtomicLong();
+
+ @Nonnull
+ final AtomicLong evictionCount = new AtomicLong();
+
+ @Nonnull
+ final AtomicLong hitCount = new AtomicLong();
+
+ @Nonnull
+ final AtomicLong missCount = new AtomicLong();
+
+ protected Stats(@Nonnull String name) {
+ super(name);
+ }
+
+ @Override
+ protected com.google.common.cache.CacheStats getCurrentStats() {
+ return new com.google.common.cache.CacheStats(
+ hitCount.get(),
+ missCount.get(),
+ loadSuccessCount.get(),
+ loadExceptionCount.get(),
+ loadTime.get(),
+ evictionCount.get());
+ }
+
+ @Override
+ public long getElementCount() {
+ return cache.size();
+ }
+
+ @Override
+ public long getMaxTotalWeight() {
+ return maximumWeight;
+ }
+
+ @Override
+ public long estimateCurrentWeight() {
+ return currentWeight.get();
+ }
}
+
}
\ No newline at end of file
Modified:
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java
(original)
+++
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentId.java
Tue May 2 15:18:56 2017
@@ -63,6 +63,9 @@ public class SegmentId implements Compar
private final long creationTime;
+ /** Callback called whenever an underlying and locally memoised segment is
accessed */
+ private final Runnable onAccess;
+
/**
* The gc generation of this segment or -1 if unknown.
*/
@@ -80,14 +83,32 @@ public class SegmentId implements Compar
*/
private volatile Segment segment;
- public SegmentId(@Nonnull SegmentStore store, long msb, long lsb) {
+ /**
+ * Create a new segment id with access tracking.
+ * @param store store this is belongs to
+ * @param msb most significant bits of this id
+ * @param lsb least significant bits of this id
+ * @param onAccess callback called whenever an underlying and locally
memoised segment is accessed.
+ */
+ public SegmentId(@Nonnull SegmentStore store, long msb, long lsb, @Nonnull
Runnable onAccess) {
this.store = store;
this.msb = msb;
this.lsb = lsb;
+ this.onAccess = onAccess;
this.creationTime = System.currentTimeMillis();
}
/**
+ * Create a new segment id without access tracking.
+ * @param store store this is belongs to
+ * @param msb most significant bits of this id
+ * @param lsb least significant bits of this id
+ */
+ public SegmentId(@Nonnull SegmentStore store, long msb, long lsb) {
+ this(store, msb, lsb, () -> {});
+ }
+
+ /**
* Checks whether this is a data segment identifier.
*
* @return {@code true} for a data segment, {@code false} otherwise
@@ -128,10 +149,11 @@ public class SegmentId implements Compar
segment = this.segment;
if (segment == null) {
log.debug("Loading segment {}", this);
- segment = store.readSegment(this);
+ return store.readSegment(this);
}
}
}
+ onAccess.run();
return segment;
}
Modified:
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
(original)
+++
jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/AbstractFileStore.java
Tue May 2 15:18:56 2017
@@ -19,8 +19,6 @@
package org.apache.jackrabbit.oak.segment.file;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.Maps.newHashMap;
import java.io.Closeable;
import java.io.File;
@@ -28,12 +26,9 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashSet;
-import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
@@ -122,7 +117,7 @@ public abstract class AbstractFileStore
this.tracker = new SegmentTracker(new SegmentIdFactory() {
@Override @Nonnull
public SegmentId newSegmentId(long msb, long lsb) {
- return new SegmentId(AbstractFileStore.this, msb, lsb);
+ return new SegmentId(AbstractFileStore.this, msb, lsb,
segmentCache::recordHit);
}
});
this.blobStore = builder.getBlobStore();
Modified:
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java
(original)
+++
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/CacheWeightsTest.java
Tue May 2 15:18:56 2017
@@ -27,8 +27,9 @@ import java.util.AbstractMap.SimpleImmut
import java.util.Map.Entry;
import java.util.UUID;
+import com.google.common.base.Supplier;
import org.apache.commons.lang.RandomStringUtils;
-import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.cache.AbstractCacheStats;
import org.apache.jackrabbit.oak.commons.StringUtils;
import org.apache.jackrabbit.oak.segment.CacheWeights.StringCacheWeigher;
import org.apache.jackrabbit.oak.segment.file.PriorityCache;
@@ -39,8 +40,6 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.base.Supplier;
-
/**
* Test/Utility class to measure size in memory for common segment-tar objects.
* <p>
@@ -320,7 +319,7 @@ public class CacheWeightsTest {
Segment segment = randomSegment(bufferSize);
cache.putSegment(segment);
}
- CacheStats stats = cache.getCacheStats();
+ AbstractCacheStats stats = cache.getCacheStats();
long elements = stats.getElementCount();
long weight = stats.estimateCurrentWeight();
return new SimpleImmutableEntry<Object, Long[]>(cache,
Modified:
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java?rev=1793528&r1=1793527&r2=1793528&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java
(original)
+++
jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/SegmentCacheTest.java
Tue May 2 15:18:56 2017
@@ -18,60 +18,60 @@
*/
package org.apache.jackrabbit.oak.segment;
+import static org.apache.jackrabbit.oak.segment.SegmentStore.EMPTY_STORE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
-import org.apache.jackrabbit.oak.cache.CacheStats;
+import org.apache.jackrabbit.oak.cache.AbstractCacheStats;
import org.junit.Test;
public class SegmentCacheTest {
- @Test
- public void putTest() {
- SegmentId id = new SegmentId(mock(SegmentStore.class), -1, -1);
- Segment segment = mock(Segment.class);
- when(segment.getSegmentId()).thenReturn(id);
- SegmentCache cache = new SegmentCache(1);
+ private final SegmentCache cache = new SegmentCache();
- cache.putSegment(segment);
- assertEquals(segment, id.getSegment());
+ private final SegmentId id1 = new SegmentId(EMPTY_STORE, -1, -1,
cache::recordHit);
+ private final Segment segment1 = mock(Segment.class);
+ private final SegmentId id2 = new SegmentId(EMPTY_STORE, -1, -2,
cache::recordHit);
+ private final Segment segment2 = mock(Segment.class);
+
+ {
+ when(segment1.getSegmentId()).thenReturn(id1);
+ when(segment2.getSegmentId()).thenReturn(id2);
}
@Test
- public void invalidateTests() {
- Segment segment1 = mock(Segment.class);
- Segment segment2 = mock(Segment.class);
- SegmentStore store = mock(SegmentStore.class);
- SegmentId id = new SegmentId(store, -1, -1);
- when(segment1.getSegmentId()).thenReturn(id);
- SegmentCache cache = new SegmentCache(1);
+ public void putTest() throws ExecutionException {
+ cache.putSegment(segment1);
+ // Segment should be cached with the segmentId and thus not trigger a
call
+ // to the (empty) node store.
+ assertEquals(segment1, cache.getSegment(id1, id1::getSegment));
+ }
+
+ @Test
+ public void invalidateTests() throws ExecutionException {
cache.putSegment(segment1);
- assertEquals(segment1, id.getSegment());
+ assertEquals(segment1, cache.getSegment(id1, id1::getSegment));
// Clearing the cache should cause an eviction call back for id
cache.clear();
- // Check that this was the case by loading a different segment
- when(store.readSegment(id)).thenReturn(segment2);
- assertEquals(segment2, id.getSegment());
+ // Check that segment1 was evicted and needs reloading through the
node store
+ AtomicBoolean cached = new AtomicBoolean(true);
+ assertEquals(segment1, cache.getSegment(id1, () -> {
+ cached.set(false);
+ return segment1;
+ }));
+ assertFalse(cached.get());
}
@Test
public void statsTest() throws Exception {
- Callable<Segment> loader = new Callable<Segment>() {
-
- @Override
- public Segment call() throws Exception {
- return mock(Segment.class);
- }
- };
-
- SegmentCache cache = new SegmentCache(1);
- CacheStats stats = cache.getCacheStats();
- SegmentId id = new SegmentId(mock(SegmentStore.class), -1, -1);
+ AbstractCacheStats stats = cache.getCacheStats();
// empty cache
assertEquals(0, stats.getElementCount());
@@ -81,27 +81,27 @@ public class SegmentCacheTest {
assertEquals(0, stats.getRequestCount());
// load
- cache.getSegment(id, loader);
+ cache.getSegment(id1, () -> segment1);
assertEquals(1, stats.getElementCount());
- assertEquals(0, stats.getLoadCount());
+ assertEquals(1, stats.getLoadCount());
assertEquals(0, stats.getHitCount());
- assertEquals(0, stats.getMissCount());
- assertEquals(0, stats.getRequestCount());
+ assertEquals(1, stats.getMissCount());
+ assertEquals(1, stats.getRequestCount());
// cache hit
- cache.getSegment(id, loader);
+ assertEquals(segment1, id1.getSegment());
assertEquals(1, stats.getElementCount());
- assertEquals(0, stats.getLoadCount());
- assertEquals(0, stats.getHitCount());
- assertEquals(0, stats.getMissCount());
- assertEquals(0, stats.getRequestCount());
+ assertEquals(1, stats.getLoadCount());
+ assertEquals(1, stats.getHitCount());
+ assertEquals(1, stats.getMissCount());
+ assertEquals(2, stats.getRequestCount());
cache.clear();
assertEquals(0, stats.getElementCount());
- assertEquals(0, stats.getLoadCount());
- assertEquals(0, stats.getHitCount());
- assertEquals(0, stats.getMissCount());
- assertEquals(0, stats.getRequestCount());
+ assertEquals(1, stats.getLoadCount());
+ assertEquals(1, stats.getHitCount());
+ assertEquals(1, stats.getMissCount());
+ assertEquals(2, stats.getRequestCount());
stats.resetStats();
assertEquals(0, stats.getElementCount());