diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/AbstractCache.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/AbstractCache.java index b6f023605d..3127f74cf5 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/AbstractCache.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/AbstractCache.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.LongAdder; import java.util.function.Function; import org.slf4j.Logger; @@ -36,15 +37,16 @@ public abstract class AbstractCache implements Cache { protected static final Logger LOG = Log.logger(Cache.class); - private volatile long hits = 0L; - private volatile long miss = 0L; + // The unit of expired time is ms + private volatile long expire; - // Default expire time(ms) - private volatile long expire = 0L; + // Enabled cache metrics may cause performance penalty + private volatile boolean enabledMetrics; + private final LongAdder hits; + private final LongAdder miss; // NOTE: the count in number of items, not in bytes private final long capacity; - private final long halfCapacity; // For user attachment private final AtomicReference attachment; @@ -58,8 +60,13 @@ public AbstractCache(long capacity) { capacity = 0L; } this.capacity = capacity; - this.halfCapacity = this.capacity >> 1; this.attachment = new AtomicReference<>(); + + this.expire = 0L; + + this.enabledMetrics = false; + this.hits = new LongAdder(); + this.miss = new LongAdder(); } @Watched(prefix = "cache") @@ -68,25 +75,13 @@ public V get(K id) { if (id == null || this.capacity <= 0L) { return null; } - V value = null; - if (this.size() <= this.halfCapacity || this.containsKey(id)) { - // Maybe the id removed by other threads and returned null value - value = this.access(id); - } - if (value == null) { - ++this.miss; - if (LOG.isDebugEnabled()) { - LOG.debug("Cache missed '{}' (miss={}, hits={})", - id, this.miss, this.hits); - } - } else { - ++this.hits; - if (LOG.isDebugEnabled()) { - LOG.debug("Cache cached '{}' (hits={}, miss={})", - id, this.hits, this.miss); - } + V value = this.access(id); + + if (this.enabledMetrics) { + this.collectMetrics(id, value); } + return value; } @@ -96,29 +91,36 @@ public V getOrFetch(K id, Function fetcher) { if (id == null || this.capacity <= 0L) { return null; } - V value = null; - if (this.size() <= this.halfCapacity || this.containsKey(id)) { - // Maybe the id removed by other threads and returned null value - value = this.access(id); + + V value = this.access(id); + + if (this.enabledMetrics) { + this.collectMetrics(id, value); + } + + // Do fetch and update the cache if cache missed + if (value == null) { + value = fetcher.apply(id); + this.update(id, value); } + return value; + } + + private void collectMetrics(K key, V value) { if (value == null) { - ++this.miss; + this.miss.add(1L); if (LOG.isDebugEnabled()) { LOG.debug("Cache missed '{}' (miss={}, hits={})", - id, this.miss, this.hits); + key, this.miss, this.hits); } - // Do fetch and update the cache - value = fetcher.apply(id); - this.update(id, value); } else { - ++this.hits; + this.hits.add(1L); if (LOG.isDebugEnabled()) { LOG.debug("Cache cached '{}' (hits={}, miss={})", - id, this.hits, this.miss); + key, this.hits, this.miss); } } - return value; } @Override @@ -199,14 +201,25 @@ public long tick() { return expireItems; } + @Override + public boolean enableMetrics(boolean enabled) { + boolean old = this.enabledMetrics; + if (!enabled) { + this.hits.reset(); + this.miss.reset(); + } + this.enabledMetrics = enabled; + return old; + } + @Override public final long hits() { - return this.hits; + return this.hits.sum(); } @Override public final long miss() { - return this.miss; + return this.miss.sum(); } @Override @@ -227,10 +240,6 @@ public T attachment() { return attachment; } - protected final long halfCapacity() { - return this.halfCapacity; - } - protected abstract V access(K id); protected abstract boolean write(K id, V value, long timeOffset); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/Cache.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/Cache.java index 2d5d82760c..7212a4b7f4 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/Cache.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/Cache.java @@ -59,6 +59,8 @@ public interface Cache { public long size(); + public boolean enableMetrics(boolean enabled); + public long hits(); public long miss(); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java index 319cf75685..f3e0f69444 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CacheManager.java @@ -51,6 +51,13 @@ public static CacheManager instance() { return INSTANCE; } + public static boolean cacheEnableMetrics(String name, boolean enabled) { + Cache cache = INSTANCE.caches.get(name); + E.checkArgument(cache != null, + "Not found cache named '%s'", name); + return cache.enableMetrics(enabled); + } + public CacheManager() { this.caches = new ConcurrentHashMap<>(); this.timer = new Timer("cache-expirer", true); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedGraphTransaction.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedGraphTransaction.java index 37d7bf8cf6..5889e56b88 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedGraphTransaction.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/CachedGraphTransaction.java @@ -115,6 +115,8 @@ private Cache cache(String prefix, String type, long capacity, } // Convert the unit from seconds to milliseconds cache.expire(expire * 1000L); + // Enable metrics for graph cache by default + cache.enableMetrics(true); return cache; } diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/OffheapCache.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/OffheapCache.java index a5761b6ce4..753a0a4a44 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/OffheapCache.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/OffheapCache.java @@ -119,7 +119,7 @@ protected boolean write(Id id, Object value, long timeOffset) { } long expireTime = this.expire(); boolean success; - if (expireTime <= 0) { + if (expireTime <= 0L) { success = this.cache.put(id, serializedValue); } else { expireTime += now() + timeOffset; diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/RamCache.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/RamCache.java index f14c758e70..4fcdddf4ac 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/RamCache.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/backend/cache/RamCache.java @@ -39,6 +39,7 @@ public class RamCache extends AbstractCache { private final LinkedQueueNonBigLock queue; private final KeyLock keyLock; + private final long halfCapacity; public RamCache() { this(DEFAULT_SIZE); @@ -47,11 +48,12 @@ public RamCache() { public RamCache(long capacity) { super(capacity); - this.keyLock = new KeyLock(); - if (capacity < 0L) { capacity = 0L; } + this.keyLock = new KeyLock(); + this.halfCapacity = capacity >> 1; + long initialCapacity = capacity >= MB ? capacity >> 10 : 256; if (initialCapacity > MAX_INIT_CAP) { initialCapacity = MAX_INIT_CAP; @@ -66,8 +68,7 @@ public RamCache(long capacity) { protected final Object access(Id id) { assert id != null; - long halfCapacity = this.halfCapacity(); - if (this.map.size() <= halfCapacity) { + if (this.map.size() <= this.halfCapacity) { LinkNode node = this.map.get(id); if (node == null) { return null; @@ -76,15 +77,21 @@ protected final Object access(Id id) { return node.value(); } + // Avoid to catch lock if cache missed + if (!this.containsKey(id)) { + return null; + } + final Lock lock = this.keyLock.lock(id); try { + // Maybe the id removed by other threads and returned null value LinkNode node = this.map.get(id); if (node == null) { return null; } // NOTE: update the queue only if the size > capacity/2 - if (this.map.size() > halfCapacity) { + if (this.map.size() > this.halfCapacity) { // Move the node from mid to tail if (this.queue.remove(node) == null) { // The node may be removed by others through dequeue() diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/unit/cache/CacheManagerTest.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/unit/cache/CacheManagerTest.java index 16a911e417..5d3c807c8c 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/unit/cache/CacheManagerTest.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/unit/cache/CacheManagerTest.java @@ -136,6 +136,48 @@ public void testCacheInstance() { this.originCaches.remove("c3"); } + @Test + public void testCacheEnableMetrics() { + // Don't mock + teardown(); + + CacheManager manager = CacheManager.instance(); + + Cache c1 = manager.cache("m1"); + Cache c2 = manager.cache("m2"); + Cache c3 = manager.offheapCache(null, "m3", 1, 11); + Cache c4 = manager.levelCache(null, "m4", 1, 1, 11); + + Assert.assertEquals(false, c1.enableMetrics(false)); + Assert.assertEquals(false, c2.enableMetrics(false)); + Assert.assertEquals(false, c3.enableMetrics(false)); + Assert.assertEquals(false, c4.enableMetrics(false)); + + Assert.assertEquals(false, CacheManager.cacheEnableMetrics("m1", true)); + Assert.assertEquals(true, c1.enableMetrics(true)); + + Assert.assertEquals(false, CacheManager.cacheEnableMetrics("m2", true)); + Assert.assertEquals(true, c2.enableMetrics(true)); + + Assert.assertEquals(false, CacheManager.cacheEnableMetrics("m3", true)); + Assert.assertEquals(true, c3.enableMetrics(true)); + + Assert.assertEquals(false, CacheManager.cacheEnableMetrics("m4", true)); + Assert.assertEquals(true, c4.enableMetrics(true)); + + Assert.assertEquals(true, CacheManager.cacheEnableMetrics("m1", false)); + Assert.assertEquals(false, c1.enableMetrics(true)); + + Assert.assertEquals(true, CacheManager.cacheEnableMetrics("m2", false)); + Assert.assertEquals(false, c2.enableMetrics(true)); + + Assert.assertEquals(true, CacheManager.cacheEnableMetrics("m3", false)); + Assert.assertEquals(false, c3.enableMetrics(true)); + + Assert.assertEquals(true, CacheManager.cacheEnableMetrics("m4", false)); + Assert.assertEquals(false, c4.enableMetrics(true)); + } + @Test public void testCacheGetPut() { final String name = "test-cache"; diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/unit/cache/CacheTest.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/unit/cache/CacheTest.java index ee6ae1c8ec..41a2d74d4b 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/unit/cache/CacheTest.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/unit/cache/CacheTest.java @@ -432,6 +432,8 @@ public void testSizeWithReachCapacity() { @Test public void testHitsAndMiss() { Cache cache = newCache(); + Assert.assertEquals(false, cache.enableMetrics(true)); + Assert.assertEquals(0L, cache.hits()); Assert.assertEquals(0L, cache.miss()); @@ -457,6 +459,47 @@ public void testHitsAndMiss() { Assert.assertEquals(2L, cache.miss()); } + @Test + public void testEnableMetrics() { + Cache cache = newCache(); + Assert.assertEquals(false, cache.enableMetrics(false)); + Assert.assertEquals(false, cache.enableMetrics(true)); + + Assert.assertEquals(0L, cache.hits()); + Assert.assertEquals(0L, cache.miss()); + + Id id = IdGenerator.of("1"); + cache.update(id, "value-1"); + Assert.assertEquals(0L, cache.hits()); + Assert.assertEquals(0L, cache.miss()); + + cache.get(IdGenerator.of("not-exist")); + Assert.assertEquals(0L, cache.hits()); + Assert.assertEquals(1L, cache.miss()); + + cache.get(IdGenerator.of("1")); + Assert.assertEquals(1L, cache.hits()); + Assert.assertEquals(1L, cache.miss()); + + cache.get(IdGenerator.of("not-exist")); + Assert.assertEquals(1L, cache.hits()); + Assert.assertEquals(2L, cache.miss()); + + cache.get(IdGenerator.of("1")); + Assert.assertEquals(2L, cache.hits()); + Assert.assertEquals(2L, cache.miss()); + + Assert.assertEquals(true, cache.enableMetrics(false)); + + Assert.assertEquals(0L, cache.hits()); + Assert.assertEquals(0L, cache.miss()); + + cache.get(IdGenerator.of("not-exist")); + cache.get(IdGenerator.of("1")); + Assert.assertEquals(0L, cache.hits()); + Assert.assertEquals(0L, cache.miss()); + } + @Test public void testExpire() { Cache cache = newCache(); @@ -693,4 +736,3 @@ public void testKeyExpired() { Assert.assertFalse(cache.containsKey(key)); } } -