You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2016/09/12 14:06:43 UTC

svn commit: r1760372 - in /jackrabbit/oak/trunk/oak-segment-tar/src: main/java/org/apache/jackrabbit/oak/segment/ main/java/org/apache/jackrabbit/oak/segment/file/ test/java/org/apache/jackrabbit/oak/segment/ test/java/org/apache/jackrabbit/oak/segment...

Author: mduerig
Date: Mon Sep 12 14:06:43 2016
New Revision: 1760372

URL: http://svn.apache.org/viewvc?rev=1760372&view=rev
Log:
OAK-4635: Improve cache eviction policy of the node deduplication cache
Replace the level based cache with a cache where mappings can have an costs associated. Use the number of child nodes of a node as cost.

Added:
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/PriorityCache.java
    jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/PriorityCacheTest.java
Removed:
    jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/NodeCacheTest.java
Modified:
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/NodeCache.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
    jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStoreBuilder.java
    jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/NodeRecordTest.java
    jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/WriteCacheManagerTest.java
    jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/DataStoreTestBase.java
    jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/TestBase.java
    jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/benchmark/BenchmarkBase.java

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/NodeCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/NodeCache.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/NodeCache.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/NodeCache.java Mon Sep 12 14:06:43 2016
@@ -19,200 +19,24 @@
 
 package org.apache.jackrabbit.oak.segment;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.Lists.newArrayList;
-import static com.google.common.collect.Sets.newHashSet;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
-import com.google.common.base.Supplier;
-import com.google.common.cache.CacheStats;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 /**
- * Partial mapping of string keys to values of type {@link RecordId}. This is
- * typically used for de-duplicating nodes that have already been persisted and thus
- * already have a {@code RecordId}.
+ * Partial mapping of {@code String} keys to values of type {@link RecordId}. The mappings
+ * can further be associated with a cost, which is a metric for the cost occurring when the
+ * given mapping is lost. Higher values represent higher costs.
  */
-public abstract class NodeCache {
-    private long hitCount;
-    private long missCount;
-    private long loadCount;
-    private long evictionCount;
+public interface NodeCache {
 
     /**
-     * Add a mapping from {@code key} to {@code value} where {@code value} is the
-     * id of a node at the given {@code depth}. Any existing mapping is replaced.
+     * Add a mapping from {@code key} to {@code value} with a given {@code cost}.
      */
-    public abstract void put(@Nonnull String key, @Nonnull RecordId value, int depth);
+    void put(@Nonnull String stableId, @Nonnull RecordId recordId, byte cost);
 
     /**
      * @return  The mapping for {@code key}, or {@code null} if none.
      */
     @CheckForNull
-    public abstract RecordId get(@Nonnull String key);
-
-    /**
-     * @return number of mappings
-     */
-    public abstract long size();
-
-    /**
-     * @return  access statistics for this cache
-     */
-    @Nonnull
-    public CacheStats getStats() {
-        return new CacheStats(hitCount, missCount, loadCount, 0, 0, evictionCount);
-    }
-
-    /**
-     * Factory method for creating {@code NodeCache} instances with a given
-     * {@code capacity} and maximal depth. The returned instances are all
-     * thread safe.
-     *
-     * Mappings with a depth exceeding {@code maxDepth} will not be added.
-     *
-     * If the number of mappings exceed {@code capacity} the maximal depth
-     * is decreased and all mappings exceeding that new value are removed.
-     *
-     * @param capacity   maximal number of mappings
-     * @param maxDepth   maximal depth
-     * @return  A new {@code RecordCache} instance of the given {@code size}.
-     */
-
-    @Nonnull
-    public static NodeCache newNodeCache(int capacity, int maxDepth) {
-        if (capacity <= 0) {
-            return new NodeCache.Empty();
-        } else {
-            return new NodeCache.Default(capacity, maxDepth);
-        }
-    }
-
-    /**
-     * @return  A factory returning {@code Node} instances of the given
-     *          {@code capacity} and {@code maxDepth} when invoked.
-     * @see #newNodeCache(int, int)
-     */
-    @Nonnull
-    public static Supplier<NodeCache> factory(int capacity, int maxDepth) {
-        if (capacity <= 0) {
-            return NodeCache.Empty.supplier();
-        } else {
-            return NodeCache.Default.supplier(capacity, maxDepth);
-        }
-    }
-
-    private static class Empty extends NodeCache {
-        static final Supplier<NodeCache> supplier() {
-            return  new Supplier<NodeCache>() {
-                @Override
-                public NodeCache get() {
-                    return new NodeCache.Empty();
-                }
-            };
-        }
-
-        @Override
-        public synchronized void put(@Nonnull String key, @Nonnull RecordId value, int depth) { }
-
-        @Override
-        public synchronized RecordId get(@Nonnull String key) {
-            super.missCount++;
-            return null;
-        }
-
-        @Override
-        public long size() {
-            return 0;
-        }
-    }
-
-    private static class Default extends NodeCache {
-        private static final Logger LOG = LoggerFactory.getLogger(Default.class);
-
-        private final int capacity;
-        private final List<Map<String, RecordId>> caches;
-
-        private int size;
-
-        private final Set<Integer> muteDepths = newHashSet();
-
-        static final Supplier<NodeCache> supplier(final int capacity, final int size) {
-            return new Supplier<NodeCache>() {
-                @Override
-                public NodeCache get() {
-                    return new NodeCache.Default(capacity, size);
-                }
-            };
-        }
-
-        Default(int capacity, int maxDepth) {
-            checkArgument(capacity > 0);
-            checkArgument(maxDepth > 0);
-            this.capacity = capacity;
-            this.caches = newArrayList();
-            for (int k = 0; k < maxDepth; k++) {
-                caches.add(new HashMap<String, RecordId>());
-            }
-        }
-
-        @Override
-        public synchronized void put(@Nonnull String key, @Nonnull RecordId value, int depth) {
-            while (size >= capacity) {
-                int d = caches.size() - 1;
-                int removed = caches.remove(d).size();
-                size -= removed;
-                super.evictionCount -= removed;
-                if (removed > 0) {
-                    LOG.info("Evicted cache at depth {} as size {} reached capacity {}. " +
-                            "New size is {}", d, size + removed, capacity, size);
-                }
-            }
-
-            if (depth < caches.size()) {
-                if (caches.get(depth).put(key, value) == null) {
-                    super.loadCount++;
-                    size++;
-                }
-            } else {
-                if (muteDepths.add(depth)) {
-                    LOG.info("Not caching {} -> {} as depth {} reaches or exceeds the maximum of {}",
-                            key, value, depth, caches.size());
-                }
-            }
-        }
-
-        @Override
-        public synchronized RecordId get(@Nonnull String key) {
-            for (Map<String, RecordId> cache : caches) {
-                if (!cache.isEmpty()) {
-                    RecordId recordId = cache.get(key);
-                    if (recordId != null) {
-                        super.hitCount++;
-                        return recordId;
-                    }
-                }
-            }
-            super.missCount++;
-            return null;
-        }
-
-        @Override
-        public synchronized long size() {
-            long size = 0;
-            for (Map<String, RecordId> cache : caches) {
-                size += cache.size();
-            }
-            return size;
-        }
-    }
-
+    RecordId get(@Nonnull String stableId);
 }

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentNodeStoreService.java Mon Sep 12 14:06:43 2016
@@ -176,20 +176,14 @@ public class SegmentNodeStoreService ext
     public static final String TEMPLATE_DEDUPLICATION_CACHE_SIZE = "templateDeduplicationCache.size";
 
     @Property(
-            intValue = 10000000,
-            label = "Node deduplication cache size  (#items)",
-            description = "Maximum number of nodes to keep in the deduplication cache"
+            intValue = 8388608,
+            label = "Node deduplication cache size (#items)",
+            description = "Maximum number of node to keep in the deduplication cache. If the supplied" +
+                    " value is not a power of 2, it will be rounded up to the next power of 2."
     )
     public static final String NODE_DEDUPLICATION_CACHE_SIZE = "nodeDeduplicationCache.size";
 
     @Property(
-            intValue = 20,
-            label = "Node deduplication cache depth  (#levels)",
-            description = "Maximum number of levels to keep in the node deduplication cache"
-    )
-    public static final String NODE_DEDUPLICATION_CACHE_DEPTH = "nodeDeduplicationCache.depth";
-
-    @Property(
             byteValue = MEMORY_THRESHOLD_DEFAULT,
             label = "Memory Multiplier",
             description = "TarMK compaction available memory multiplier needed to run compaction"
@@ -396,7 +390,6 @@ public class SegmentNodeStoreService ext
                 .withStringDeduplicationCacheSize(getStringDeduplicationCacheSize())
                 .withTemplateDeduplicationCacheSize(getTemplateDeduplicationCacheSize())
                 .withNodeDeduplicationCacheSize(getNodeDeduplicationCacheSize())
-                .withNodeDeduplicationDepth(getNodeDeduplicationDepth())
                 .withMaxFileSize(getMaxFileSize())
                 .withMemoryMapping(getMode().equals("64"))
                 .withGCMonitor(gcMonitor)
@@ -710,11 +703,9 @@ public class SegmentNodeStoreService ext
     }
 
     private int getNodeDeduplicationCacheSize() {
-        return Integer.parseInt(getCacheSize(NODE_DEDUPLICATION_CACHE_SIZE));
-    }
-
-    private int getNodeDeduplicationDepth() {
-        return Integer.parseInt(getCacheSize(NODE_DEDUPLICATION_CACHE_DEPTH));
+        // Round to the next power of 2
+        int size = Math.max(1, Integer.parseInt(getCacheSize(NODE_DEDUPLICATION_CACHE_SIZE)));
+        return 1 << (32 - Integer.numberOfLeadingZeros(size - 1));
     }
 
     private String getMaxFileSizeProperty() {

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/SegmentWriter.java Mon Sep 12 14:06:43 2016
@@ -33,6 +33,8 @@ import static com.google.common.collect.
 import static com.google.common.collect.Maps.newHashMap;
 import static com.google.common.io.ByteStreams.read;
 import static java.lang.Integer.getInteger;
+import static java.lang.Long.numberOfLeadingZeros;
+import static java.lang.Math.min;
 import static java.lang.System.nanoTime;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyMap;
@@ -160,6 +162,16 @@ public class SegmentWriter {
     }
 
     /**
+     * Get occupancy information for the node deduplication cache indicating occupancy and
+     * evictions per priority.
+     * @return  occupancy information for the node deduplication cache.
+     */
+    @CheckForNull
+    public String getNodeCacheOccupancyInfo() {
+        return cacheManager.getNodeCacheOccupancyInfo();
+    }
+
+    /**
      * @return  Statistics for node write times (in ns).
      * The statistics are collected with a window size defined by {@link #NODE_WRITER_STATS_WINDOW}
      * @see #getNodeCompactTimeStats()
@@ -727,7 +739,7 @@ public class SegmentWriter {
 
             // inline the remaining data as block records
             while (pos < data.length) {
-                int len = Math.min(BLOCK_SIZE, data.length - pos);
+                int len = min(BLOCK_SIZE, data.length - pos);
                 blockIds.add(writeBlock(data, pos, len));
                 pos += len;
             }
@@ -988,9 +1000,10 @@ public class SegmentWriter {
             } finally {
                 if (nodeWriteStats.isCompactOp) {
                     nodeCompactTimeStats.addValue(nanoTime() - nodeWriteStats.startTime);
-                    LOG.info(nodeWriteStats.toString());
+                    LOG.info("{}", nodeWriteStats);
                 } else {
                     nodeWriteTimeStats.addValue(nanoTime() - nodeWriteStats.startTime);
+                    LOG.debug("{}", nodeWriteStats);
                 }
             }
         }
@@ -1016,12 +1029,17 @@ public class SegmentWriter {
                 // generation (e.g. due to compaction). Put it into the cache for
                 // deduplication of hard links to it (e.g. checkpoints).
                 SegmentNodeState sns = (SegmentNodeState) state;
-                nodeCache.put(sns.getStableId(), recordId, depth);
+                nodeCache.put(sns.getStableId(), recordId, cost(sns));
                 nodeWriteStats.isCompactOp = true;
             }
             return recordId;
         }
 
+        private byte cost(SegmentNodeState node) {
+            long childCount = node.getChildNodeCount(Long.MAX_VALUE);
+            return (byte) (Byte.MIN_VALUE + 64 - numberOfLeadingZeros(childCount));
+        }
+
         private RecordId writeNodeUncached(@Nonnull NodeState state, int depth) throws IOException {
             ModifiedNodeState after = null;
 

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/WriterCacheManager.java Mon Sep 12 14:06:43 2016
@@ -38,6 +38,7 @@ import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.cache.CacheStats;
 import org.apache.jackrabbit.oak.api.jmx.CacheStatsMBean;
+import org.apache.jackrabbit.oak.segment.file.PriorityCache;
 
 /**
  * Instances of this class manage the deduplication caches used
@@ -63,18 +64,11 @@ public abstract class WriterCacheManager
             "oak.tar.templatesCacheSize", 3000);
 
     /**
-     * Default capacity of the node cache.
+     * Default size of the node deduplication cache.
      * @see #getNodeCache(int)
      */
-    public static final int DEFAULT_NODE_CACHE_CAPACITY = getInteger(
-            "oak.tar.nodeCacheCapacity", 1000000);
-
-    /**
-     * Default maximal depth of the node cache.
-     * @see #getNodeCache(int)
-     */
-    public static final int DEFAULT_NODE_CACHE_DEPTH = getInteger(
-            "oak.tar.nodeCacheDepth", 20);
+    public static final int DEFAULT_NODE_CACHE_SIZE = getInteger(
+            "oak.tar.nodeCacheSize", 8388608);
 
     /**
      * @param generation
@@ -122,6 +116,14 @@ public abstract class WriterCacheManager
     }
 
     /**
+     * Get occupancy information for the node deduplication cache indicating occupancy and
+     * evictions per priority.
+     * @return  occupancy information for the node deduplication cache.
+     */
+    @CheckForNull
+    public String getNodeCacheOccupancyInfo() { return null; }
+
+    /**
      * This implementation of {@link WriterCacheManager} returns empty caches
      * of size 0.
      * @see #INSTANCE
@@ -135,7 +137,6 @@ public abstract class WriterCacheManager
 
         private final RecordCache<String> stringCache = newRecordCache(0);
         private final RecordCache<Template> templateCache = newRecordCache(0);
-        private final NodeCache nodeCache = NodeCache.newNodeCache(0, 0);
 
         private Empty() {}
 
@@ -156,18 +157,26 @@ public abstract class WriterCacheManager
         }
 
         /**
-         * @return  empty cache of size 0
+         * @return  a {@code NodeCache} cache that is always empty
          */
+        @Nonnull
         @Override
         public NodeCache getNodeCache(int generation) {
-            return nodeCache;
+            return new NodeCache() {
+                @Override
+                public void put(@Nonnull String stableId, @Nonnull RecordId recordId, byte cost) { }
+
+                @CheckForNull
+                @Override
+                public RecordId get(@Nonnull String stableId) { return null; }
+            };
         }
     }
 
     /**
      * This implementation of {@link WriterCacheManager} returns
      * {@link RecordCache} instances for the string and template cache
-     * and {@link NodeCache} instances for the node cache.
+     * and {@link NodeCache} instance for the node cache.
      */
     public static class Default extends WriterCacheManager {
         /**
@@ -186,7 +195,7 @@ public abstract class WriterCacheManager
          * Cache of recently stored nodes to avoid duplicating linked nodes (i.e. checkpoints)
          * during compaction.
          */
-        private final Generations<NodeCache> nodeCaches;
+        private final Supplier<PriorityCache<String, RecordId>> nodeCache;
 
         /**
          * New instance using the passed factories for creating cache instances.
@@ -200,21 +209,22 @@ public abstract class WriterCacheManager
         public Default(
                 @Nonnull Supplier<RecordCache<String>> stringCacheFactory,
                 @Nonnull Supplier<RecordCache<Template>> templateCacheFactory,
-                @Nonnull Supplier<NodeCache> nodeCacheFactory) {
+                @Nonnull Supplier<PriorityCache<String, RecordId>> nodeCacheFactory) {
             this.stringCaches = new Generations<>(stringCacheFactory);
             this.templateCaches = new Generations<>(templateCacheFactory);
-            this.nodeCaches = new Generations<>(nodeCacheFactory);
+            this.nodeCache = memoize(nodeCacheFactory);
         }
 
         /**
          * New instance using the default factories {@link RecordCache#factory(int)}
-         * and {@link NodeCache#factory(int, int)} with the sizes
-         * {@link #DEFAULT_STRING_CACHE_SIZE} and {@link #DEFAULT_TEMPLATE_CACHE_SIZE}.
+         * and {@link PriorityCache#factory(int)} with the sizes
+         * {@link #DEFAULT_STRING_CACHE_SIZE}, {@link #DEFAULT_TEMPLATE_CACHE_SIZE}
+         * and {@link #DEFAULT_NODE_CACHE_SIZE}.
          */
         public Default() {
             this(RecordCache.<String>factory(DEFAULT_STRING_CACHE_SIZE),
                  RecordCache.<Template>factory(DEFAULT_TEMPLATE_CACHE_SIZE),
-                 NodeCache.factory(DEFAULT_NODE_CACHE_CAPACITY, DEFAULT_NODE_CACHE_DEPTH));
+                 PriorityCache.<String, RecordId>factory(DEFAULT_NODE_CACHE_SIZE));
         }
 
         private static class Generations<T> implements Iterable<T> {
@@ -265,10 +275,21 @@ public abstract class WriterCacheManager
             return templateCaches.getGeneration(generation);
         }
 
-        @Nonnull
         @Override
-        public NodeCache getNodeCache(int generation) {
-            return nodeCaches.getGeneration(generation);
+        @Nonnull
+        public NodeCache getNodeCache(final int generation) {
+            return new NodeCache() {
+                @Override
+                public void put(@Nonnull String stableId, @Nonnull RecordId recordId, byte cost) {
+                    nodeCache.get().put(stableId, recordId, generation, cost);
+                }
+
+                @CheckForNull
+                @Override
+                public RecordId get(@Nonnull String stableId) {
+                    return nodeCache.get().get(stableId, generation);
+                }
+            };
         }
 
         @CheckForNull
@@ -319,37 +340,23 @@ public abstract class WriterCacheManager
         @Override
         public CacheStatsMBean getNodeCacheStats() {
             return new RecordCacheStats("Node deduplication cache stats",
-                    accumulateNodeCacheStats(nodeCaches), accumulateNodeCacheSizes(nodeCaches));
+                    new Supplier<CacheStats>() {
+                        @Override
+                        public CacheStats get() {
+                            return nodeCache.get().getStats();
+                        }
+                    },
+                    new Supplier<Long>() {
+                        @Override
+                        public Long get() {
+                            return nodeCache.get().size();
+                        }
+                    });
         }
 
-        @Nonnull
-        private static <T> Supplier<CacheStats> accumulateNodeCacheStats(
-                final Iterable<NodeCache> caches) {
-            return new Supplier<CacheStats>() {
-                @Override
-                public CacheStats get() {
-                    CacheStats stats = new CacheStats(0, 0, 0, 0, 0, 0);
-                    for (NodeCache cache : caches) {
-                        stats = stats.plus(cache.getStats());
-                    }
-                    return stats;
-                }
-            };
-        }
-
-        @Nonnull
-        public static <T> Supplier<Long> accumulateNodeCacheSizes(
-                final Iterable<NodeCache> caches) {
-            return new Supplier<Long>() {
-                @Override
-                public Long get() {
-                    long size = 0;
-                    for (NodeCache cache : caches) {
-                        size += cache.size();
-                    }
-                    return size;
-                }
-            };
+        @Override
+        public String getNodeCacheOccupancyInfo() {
+            return nodeCache.get().toString();
         }
 
         /**
@@ -359,7 +366,6 @@ public abstract class WriterCacheManager
         protected final void evictCaches(Predicate<Integer> generations) {
             stringCaches.evictGenerations(generations);
             templateCaches.evictGenerations(generations);
-            nodeCaches.evictGenerations(generations);
         }
     }
 }

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStore.java Mon Sep 12 14:06:43 2016
@@ -534,10 +534,12 @@ public class FileStore implements Segmen
         if (sufficientEstimatedGain) {
             if (!gcOptions.isPaused()) {
                 logAndClear(segmentWriter.getNodeWriteTimeStats(), segmentWriter.getNodeCompactTimeStats());
+                log(segmentWriter.getNodeCacheOccupancyInfo());
                 if (compact()) {
                     cleanupNeeded.set(cleanup);
                 }
                 logAndClear(segmentWriter.getNodeWriteTimeStats(), segmentWriter.getNodeCompactTimeStats());
+                log(segmentWriter.getNodeCacheOccupancyInfo());
             } else {
                 gcListener.skipped("TarMK GC #{}: compaction paused", GC_COUNT);
             }
@@ -553,6 +555,12 @@ public class FileStore implements Segmen
         nodeCompactTimeStats.clear();
     }
 
+    private static void log(@CheckForNull String nodeCacheOccupancyInfo) {
+        if (nodeCacheOccupancyInfo != null) {
+            log.info("NodeCache occupancy: {}", nodeCacheOccupancyInfo);
+        }
+    }
+
     private static String toString(DescriptiveStatistics statistics) {
         DecimalFormat sci = new DecimalFormat("##0.0E0");
         DecimalFormatSymbols symbols = sci.getDecimalFormatSymbols();

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStoreBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStoreBuilder.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStoreBuilder.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/FileStoreBuilder.java Mon Sep 12 14:06:43 2016
@@ -24,8 +24,7 @@ import static com.google.common.base.Pre
 import static org.apache.jackrabbit.oak.segment.CachingSegmentReader.DEFAULT_STRING_CACHE_MB;
 import static org.apache.jackrabbit.oak.segment.CachingSegmentReader.DEFAULT_TEMPLATE_CACHE_MB;
 import static org.apache.jackrabbit.oak.segment.SegmentCache.DEFAULT_SEGMENT_CACHE_MB;
-import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_NODE_CACHE_CAPACITY;
-import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_NODE_CACHE_DEPTH;
+import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_NODE_CACHE_SIZE;
 import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_STRING_CACHE_SIZE;
 import static org.apache.jackrabbit.oak.segment.WriterCacheManager.DEFAULT_TEMPLATE_CACHE_SIZE;
 import static org.apache.jackrabbit.oak.segment.compaction.SegmentGCOptions.defaultGCOptions;
@@ -37,8 +36,8 @@ import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 
 import com.google.common.base.Predicate;
-import org.apache.jackrabbit.oak.segment.NodeCache;
 import org.apache.jackrabbit.oak.segment.RecordCache;
+import org.apache.jackrabbit.oak.segment.RecordId;
 import org.apache.jackrabbit.oak.segment.Template;
 import org.apache.jackrabbit.oak.segment.WriterCacheManager;
 import org.apache.jackrabbit.oak.segment.compaction.LoggingGCMonitor;
@@ -75,9 +74,7 @@ public class FileStoreBuilder {
 
     private int templateDeduplicationCacheSize = DEFAULT_TEMPLATE_CACHE_SIZE;
 
-    private int nodeDeduplicationCacheSize = DEFAULT_NODE_CACHE_CAPACITY;
-
-    private int nodeDeduplicationCacheDepth = DEFAULT_NODE_CACHE_DEPTH;
+    private int nodeDeduplicationCacheSize = DEFAULT_NODE_CACHE_SIZE;
 
     private boolean memoryMapping = FileStore.MEMORY_MAPPING_DEFAULT;
 
@@ -241,7 +238,7 @@ public class FileStoreBuilder {
 
     /**
      * Number of items to keep in the node deduplication cache
-     * @param nodeDeduplicationCacheSize  None negative cache size
+     * @param nodeDeduplicationCacheSize  None negative cache size. Must be a power of 2.
      * @return this instance
      */
     @Nonnull
@@ -251,17 +248,6 @@ public class FileStoreBuilder {
     }
 
     /**
-     * Maximal depth of the node deduplication cache
-     * @param nodeDeduplicationCacheDepth
-     * @return this instance
-     */
-    @Nonnull
-    public FileStoreBuilder withNodeDeduplicationDepth(int nodeDeduplicationCacheDepth) {
-        this.nodeDeduplicationCacheDepth = nodeDeduplicationCacheDepth;
-        return this;
-    }
-
-    /**
      * Turn memory mapping on or off
      * @param memoryMapping
      * @return this instance
@@ -422,8 +408,7 @@ public class FileStoreBuilder {
     WriterCacheManager getCacheManager() {
         if (cacheManager == null) {
             cacheManager = new EvictingWriteCacheManager(
-                    stringDeduplicationCacheSize, templateDeduplicationCacheSize,
-                    nodeDeduplicationCacheSize, nodeDeduplicationCacheDepth);
+                    stringDeduplicationCacheSize, templateDeduplicationCacheSize, nodeDeduplicationCacheSize);
         }
         return cacheManager;
     }
@@ -440,18 +425,16 @@ public class FileStoreBuilder {
                 ", stringDeduplicationCacheSize=" + stringDeduplicationCacheSize +
                 ", templateDeduplicationCacheSize=" + templateDeduplicationCacheSize +
                 ", nodeDeduplicationCacheSize=" + nodeDeduplicationCacheSize +
-                ", nodeDeduplicationCacheDepth=" + nodeDeduplicationCacheDepth +
                 ", memoryMapping=" + memoryMapping +
                 ", gcOptions=" + gcOptions +
                 '}';
     }
 
     private static class EvictingWriteCacheManager extends WriterCacheManager.Default {
-        public EvictingWriteCacheManager(int stringCacheSize, int templateCacheSize,
-                                         int nodeCacheCapacity, int nodeCacheDepth) {
+        public EvictingWriteCacheManager(int stringCacheSize, int templateCacheSize, int nodeCacheSize) {
             super(RecordCache.<String>factory(stringCacheSize),
                 RecordCache.<Template>factory(templateCacheSize),
-                NodeCache.factory(nodeCacheCapacity, nodeCacheDepth));
+                PriorityCache.<String, RecordId>factory(nodeCacheSize));
         }
 
         void evictOldGeneration(final int newGeneration) {

Added: jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/PriorityCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/PriorityCache.java?rev=1760372&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/PriorityCache.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/file/PriorityCache.java Mon Sep 12 14:06:43 2016
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.segment.file;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.Integer.bitCount;
+import static java.lang.Integer.numberOfTrailingZeros;
+import static java.util.Arrays.fill;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+import com.google.common.base.Supplier;
+import com.google.common.cache.CacheStats;
+
+/**
+ * {@code PriorityCache} implements a partial mapping from keys of type {@code K} to values
+ * of type  {@code V}. Mappings are associates with a cost, which states how expensive it is
+ * to recreate that mapping. This cache uses the cost such that mappings with a higher cost
+ * have a lower chance of being evicted than mappings with a lower cost. When an item from
+ * this cache is successfully looked up its cost is incremented by one, unless it has reached
+ * its maximum cost of {@link Byte#MAX_VALUE} already.
+ * <p>
+ * Additionally this cache tracks a generation for mappings. Mappings of later generations
+ * always take precedence over mappings of earlier generations. That is, putting a mapping of
+ * a later generation into the cache can cause any mapping of an earlier generation to be evicted
+ * regardless of its cost.
+ * <p>
+ * This cache uses rehashing to resolve clashes. The number of rehashes is configurable. When
+ * a clash cannot be resolved by rehashing the given number of times the put operation fails.
+ * <p>
+ * This cache is thread safe.
+ * @param <K>  type of the keys
+ * @param <V>  type of the values
+ */
+public class PriorityCache<K, V> {
+    private final int rehash;
+    private final Entry<?,?>[] entries;
+    private final int[] costs = new int[256];
+    private final int[] evictions = new int[256];
+
+    private long hitCount;
+    private long missCount;
+    private long loadCount;
+    private long evictionCount;
+    private long size;
+
+    /**
+     * Static factory for creating new {@code PriorityCache} instances.
+     * @param size  size of the cache. Must be a power of 2.
+     * @return  a new {@code PriorityCache} instance of the given {@code size}.
+     */
+    public static <K, V> Supplier<PriorityCache<K, V>> factory(final int size) {
+        checkArgument(bitCount(size) == 1);
+        return new Supplier<PriorityCache<K, V>>() {
+            @Override
+            public PriorityCache<K, V> get() {
+                return new PriorityCache<>(size);
+            }
+        };
+    }
+
+    private static class Entry<K, V> {
+        static Entry<Void, Void> NULL = new Entry<>(null, null, -1, Byte.MIN_VALUE);
+
+        final K key;
+        final V value;
+        final int generation;
+        byte cost;
+
+        public Entry(K key, V value, int generation, byte cost) {
+            this.key = key;
+            this.value = value;
+            this.generation = generation;
+            this.cost = cost;
+        }
+
+        @Override
+        public String toString() {
+            return this == NULL
+                ? "NULL"
+                : "Entry{" + key + "->" + value + " @" + generation + ", $" + cost + "}";
+        }
+    }
+
+    /**
+     * Create a new instance of the given {@code size}. {@code rehash} specifies the number
+     * of rehashes to resolve a clash.
+     * @param size      Size of the cache. Must be a power of {@code 2}.
+     * @param rehash    Number of rehashes. Must be greater or equal to {@code 0} and
+     *                  smaller than {@code 32 - numberOfTrailingZeros(size)}.
+     */
+    public PriorityCache(int size, int rehash) {
+        checkArgument(bitCount(size) == 1);
+        checkArgument(rehash >= 0);
+        checkArgument(rehash < 32 - numberOfTrailingZeros(size));
+        this.rehash = rehash;
+        entries = new Entry<?,?>[size];
+        fill(entries, Entry.NULL);
+    }
+
+    /**
+     * Create a new instance of the given {@code size}. The number of rehashes is
+     * the maximum number allowed by the given {@code size}. ({@code 31 - numberOfTrailingZeros(size)}.
+     * @param size      Size of the cache. Must be a power of {@code 2}.
+     */
+    public PriorityCache(int size) {
+        this(size, 31 - numberOfTrailingZeros(size));
+    }
+
+    private int project(int hashCode, int iteration) {
+        return (hashCode >> iteration) & (entries.length - 1);
+    }
+
+    /**
+     * @return  the number of mappings in this cache.
+     */
+    public long size() {
+        return size;
+    }
+
+    /**
+     * Add a mapping to the cache.
+     * @param key            the key of the mapping
+     * @param value          the value of the mapping
+     * @param generation     the generation of the mapping
+     * @param initialCost    the initial cost associated with this mapping
+     * @return  {@code true} if the mapping has been added, {@code false} otherwise.
+     */
+    public synchronized boolean put(@Nonnull K key, @Nonnull V value, int generation, byte initialCost) {
+        int hashCode = key.hashCode();
+        byte cheapest = initialCost;
+        int index = -1;
+        boolean eviction = false;
+        for (int k = 0; k <= rehash; k++) {
+            int i = project(hashCode, k);
+            Entry<?, ?> entry = entries[i];
+            if (entry == Entry.NULL) {
+                // Empty slot -> use this index
+                index = i;
+                break;
+            } else if (entry.generation <= generation && key.equals(entry.key)) {
+                // Key exists and generation is greater or equal -> use this index and boost the cost
+                index = i;
+                initialCost = entry.cost;
+                if (initialCost < Byte.MAX_VALUE) {
+                    initialCost++;
+                }
+                break;
+            } else if (entry.generation < generation) {
+                // Old generation -> use this index
+                index = i;
+                break;
+            } else if (entry.cost < cheapest) {
+                // Candidate slot, keep on searching for even cheaper slots
+                cheapest = entry.cost;
+                index = i;
+                eviction = true;
+            }
+        }
+
+        if (index >= 0) {
+            Entry<?, ?> old = entries[index];
+            entries[index] = new Entry<>(key, value, generation, initialCost);
+            loadCount++;
+            costs[initialCost - Byte.MIN_VALUE]++;
+            if (old != Entry.NULL) {
+                costs[old.cost - Byte.MIN_VALUE]--;
+                if (eviction) {
+                    evictions[old.cost - Byte.MIN_VALUE]++;
+                    evictionCount++;
+                }
+            } else {
+                size++;
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Look up a mapping from this cache by its {@code key} and {@code generation}.
+     * @param key         key of the mapping to look up
+     * @param generation  generation of the mapping to look up
+     * @return  the mapping for {@code key} and {@code generation} or {@code null} if this
+     *          cache does not contain such a mapping.
+     */
+    @SuppressWarnings("unchecked")
+    @CheckForNull
+    public synchronized V get(@Nonnull K key, int generation) {
+        int hashCode = key.hashCode();
+        for (int k = 0; k <= rehash; k++) {
+            int i = project(hashCode, k);
+            Entry<?, ?> entry = entries[i];
+            if (generation == entry.generation && key.equals(entry.key)) {
+                if (entry.cost < Byte.MAX_VALUE) {
+                    costs[entry.cost - Byte.MIN_VALUE]--;
+                    entry.cost++;
+                    costs[entry.cost - Byte.MIN_VALUE]++;
+                }
+                hitCount++;
+                return (V) entry.value;
+            }
+        }
+        missCount++;
+        return null;
+    }
+
+    @Override
+    public synchronized String toString() {
+        return "PriorityCache" +
+            "{ costs=" + toString(costs) +
+            ", evictions=" + toString(evictions) + " }";
+    }
+
+    private static String toString(int[] ints) {
+        StringBuilder b = new StringBuilder("[");
+        String sep = "";
+        for (int i = 0; i < ints.length; i++) {
+            if (ints[i] > 0) {
+                b.append(sep).append(i).append("->").append(ints[i]);
+                sep = ",";
+            }
+        }
+        return b.append(']').toString();
+    }
+
+    /**
+     * @return  access statistics for this cache
+     */
+    @Nonnull
+    public CacheStats getStats() {
+        return new CacheStats(hitCount, missCount, loadCount, 0, 0, evictionCount);
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/NodeRecordTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/NodeRecordTest.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/NodeRecordTest.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/NodeRecordTest.java Mon Sep 12 14:06:43 2016
@@ -186,7 +186,6 @@ public class NodeRecordTest {
             public NodeCache getNodeCache(int generation) {
                 return defaultCache.getNodeCache(generation);
             }
-
         };
     }
 

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/WriteCacheManagerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/WriteCacheManagerTest.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/WriteCacheManagerTest.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/WriteCacheManagerTest.java Mon Sep 12 14:06:43 2016
@@ -21,9 +21,7 @@ package org.apache.jackrabbit.oak.segmen
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 
-import com.google.common.base.Supplier;
 import org.apache.jackrabbit.oak.segment.WriterCacheManager.Default;
 import org.apache.jackrabbit.oak.segment.WriterCacheManager.Empty;
 import org.junit.Test;
@@ -34,7 +32,6 @@ public class WriteCacheManagerTest {
     public void emptyGenerations() {
         WriterCacheManager cache = Empty.INSTANCE;
         assertEquals(cache.getTemplateCache(0), cache.getTemplateCache(1));
-        assertEquals(cache.getNodeCache(0), cache.getNodeCache(1));
         assertEquals(cache.getStringCache(0), cache.getStringCache(1));
     }
 
@@ -42,26 +39,7 @@ public class WriteCacheManagerTest {
     public void nonEmptyGenerations() {
         WriterCacheManager cache = new Default();
         assertNotEquals(cache.getTemplateCache(0), cache.getTemplateCache(1));
-        assertNotEquals(cache.getNodeCache(0), cache.getNodeCache(1));
         assertNotEquals(cache.getStringCache(0), cache.getStringCache(1));
     }
 
-    @Test
-    public void factory() {
-        WriterCacheManager cache = new Default(new Supplier<RecordCache<String>>() {
-            int accessCount = 2;
-            @Override
-            public RecordCache<String> get() {
-                assertTrue("Factory should only be invoked once per generation", --accessCount >= 0);
-                return RecordCache.<String>factory(42).get();
-            }
-        }, RecordCache.<Template>factory(42), NodeCache.factory(42, 2));
-
-        cache.getStringCache(0);
-        cache.getStringCache(0);
-        cache.getStringCache(1);
-        cache.getStringCache(1);
-        cache.getStringCache(1);
-        cache.getStringCache(0);
-    }
 }

Added: jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/PriorityCacheTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/PriorityCacheTest.java?rev=1760372&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/PriorityCacheTest.java (added)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/file/PriorityCacheTest.java Mon Sep 12 14:06:43 2016
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.segment.file;
+
+import static java.lang.Integer.valueOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class PriorityCacheTest {
+
+    @Test(expected = IllegalArgumentException.class)
+    public void illegalSize() {
+        new PriorityCache<String, String>(42, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void zeroSize() {
+        new PriorityCache<String, String>(0, 0);
+    }
+
+    @Test
+    public void legalHash() {
+        for (int k = 0; k < 8; k++) {
+            new PriorityCache<String, String>(0x1000000, k);
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void illegalHash() {
+        new PriorityCache<String, String>(0x1000000, 9);
+    }
+
+    @Test
+    public void singletonCache() {
+        PriorityCache<String, Integer> cache = new PriorityCache<String, Integer>(1, 0);
+        assertTrue(cache.put("one", 1, 0, (byte) 0));
+
+        // Cache is full -> cannot put another key of the same cost
+        assertFalse(cache.put("two", 2, 0, (byte) 0));
+
+        // Retrieving "one" leads to a cache hit increasing this key's cost to 1
+        assertEquals(valueOf(1), cache.get("one", 0));
+        assertNull(cache.get("one", 1));
+        assertNull(cache.get("two", 0));
+
+        // Inserting "two" only succeeds for cost 2, which is bigger than "one"'s cost of 1
+        assertFalse(cache.put("two", 2, 0, (byte) 1));
+        assertTrue(cache.put("two", 2, 0, (byte) 2));
+        assertEquals(valueOf(2), cache.get("two", 0));
+        assertNull(cache.get("two", 1));
+        assertNull(cache.get("one", 0));
+    }
+
+    @Test
+    public void readWrite() {
+        PriorityCache<String, Integer> cache = new PriorityCache<String, Integer>(128, 0);
+        for (int k = 0; k < 128; k++) {
+            if (cache.put("key-" + k, k, 0, (byte) 0)) {
+                assertEquals(Integer.valueOf(k), cache.get("key-" + k, 0));
+                assertNull(cache.get("key-" + k, 1));
+            } else {
+                assertNull(cache.get("key-" + k, 0));
+            }
+        }
+
+        for (int k = 0; k < 128; k++) {
+            Integer value = cache.get("key-" + k, 0);
+            if (value != null) {
+                assertEquals(Integer.valueOf(k), value);
+            }
+        }
+    }
+
+    @Test
+    public void updateKey() {
+        PriorityCache<String, Integer> cache = new PriorityCache<String, Integer>(1, 0);
+
+        assertTrue(cache.put("one", 1, 0, (byte) 0));
+
+        // Cache is full -> cannot put another key of the same cost
+        assertFalse(cache.put("two", 2, 0, (byte) 0));
+
+        // But updating an existing key works and boosts its cost to 1
+        assertTrue(cache.put("one", 1, 0, (byte) 0));
+
+        // such that adding another key only works at cost 2 and greater
+        assertFalse(cache.put("two", 2, 0, (byte) 1));
+        assertTrue(cache.put("two", 2, 0, (byte) 2));
+    }
+
+    @Test
+    public void updateWithNewGeneration() {
+        PriorityCache<String, Integer> cache = new PriorityCache<String, Integer>(1, 0);
+        assertTrue(cache.put("one", 1, 0, (byte) 0));
+
+        // Cache is full but we can still put a key of a higher generation
+        assertTrue(cache.put("two", 2, 1, (byte) 0));
+        assertNull(cache.get("one", 0));
+
+        // Cannot put a key of a lower generation
+        assertFalse(cache.put("two", 2, 0, (byte) 0));
+
+        // But one of the same generation
+        assertTrue(cache.put("two", 2, 1, (byte) 0));
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/DataStoreTestBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/DataStoreTestBase.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/DataStoreTestBase.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/DataStoreTestBase.java Mon Sep 12 14:06:43 2016
@@ -75,7 +75,7 @@ public class DataStoreTestBase extends T
         return FileStoreBuilder.fileStoreBuilder(d)
                 .withMaxFileSize(1)
                 .withMemoryMapping(false)
-                .withNodeDeduplicationCacheSize(0)
+                .withNodeDeduplicationCacheSize(1)
                 .withSegmentCacheSize(0)
                 .withStringCacheSize(0)
                 .withTemplateCacheSize(0)

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/TestBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/TestBase.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/TestBase.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/TestBase.java Mon Sep 12 14:06:43 2016
@@ -84,7 +84,7 @@ public class TestBase {
         return fileStoreBuilder(directory)
                 .withMaxFileSize(1)
                 .withMemoryMapping(false)
-                .withNodeDeduplicationCacheSize(0)
+                .withNodeDeduplicationCacheSize(1)
                 .withSegmentCacheSize(0)
                 .withStringCacheSize(0)
                 .withTemplateCacheSize(0)

Modified: jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/benchmark/BenchmarkBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/benchmark/BenchmarkBase.java?rev=1760372&r1=1760371&r2=1760372&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/benchmark/BenchmarkBase.java (original)
+++ jackrabbit/oak/trunk/oak-segment-tar/src/test/java/org/apache/jackrabbit/oak/segment/standby/benchmark/BenchmarkBase.java Mon Sep 12 14:06:43 2016
@@ -78,9 +78,15 @@ public class BenchmarkBase {
     }
 
     private static FileStore newFileStore(File directory, ScheduledExecutorService executor) throws Exception {
-        return fileStoreBuilder(directory).withMaxFileSize(1).withMemoryMapping(false).withNodeDeduplicationCacheSize(0)
-                .withSegmentCacheSize(0).withStringCacheSize(0).withTemplateCacheSize(0)
-                .withStatisticsProvider(new DefaultStatisticsProvider(executor)).build();
+        return fileStoreBuilder(directory)
+                .withMaxFileSize(1)
+                .withMemoryMapping(false)
+                .withNodeDeduplicationCacheSize(1)
+                .withSegmentCacheSize(0)
+                .withStringCacheSize(0)
+                .withTemplateCacheSize(0)
+                .withStatisticsProvider(new DefaultStatisticsProvider(executor))
+                .build();
     }
 
     protected FileStore setupPrimary(File directory, ScheduledExecutorService executor) throws Exception {