Author: mduerig
Date: Wed Apr 20 10:04:46 2016
New Revision: 1740094

URL: http://svn.apache.org/viewvc?rev=1740094&view=rev
Log:
OAK-3348: Cross gc sessions might introduce references to pre-compacted segments
Deduplication of node states during compaction

Modified:
    
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java
    
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordCache.java
    
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java
    
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
    
jackrabbit/oak/trunk/oak-segment-next/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java

Modified: 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java?rev=1740094&r1=1740093&r2=1740094&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Compactor.java
 Wed Apr 20 10:04:46 2016
@@ -20,12 +20,10 @@ import static org.apache.jackrabbit.oak.
 
 import java.io.IOException;
 
-import javax.annotation.Nonnull;
-
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
 import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.plugins.segment.compaction.CompactionStrategy;
+import org.apache.jackrabbit.oak.plugins.segment.RecordCache.Cache;
 import org.apache.jackrabbit.oak.spi.state.ApplyDiff;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -42,6 +40,8 @@ public class Compactor {
 
     private final SegmentTracker tracker;
 
+    private final int gcGeneration;
+
     private final SegmentWriter writer;
 
     private final ProgressTracker progress = new ProgressTracker();
@@ -60,20 +60,27 @@ public class Compactor {
 
     Compactor(SegmentTracker tracker, Supplier<Boolean> cancel) {
         this.tracker = tracker;
+        this.gcGeneration = -1;
         this.writer = tracker.getWriter();
         this.cancel = cancel;
     }
 
-    public Compactor(SegmentTracker tracker, CompactionStrategy 
compactionStrategy, Supplier<Boolean> cancel) {
+    public Compactor(SegmentTracker tracker, final Cache<String> cache, 
Supplier<Boolean> cancel) {
         this.tracker = tracker;
-        this.writer = createSegmentWriter(tracker);
+        this.gcGeneration = tracker.getGcGen() + 1;
+        this.writer = new SegmentWriter(tracker.getStore(), 
tracker.getSegmentVersion(),
+            new SegmentBufferWriter(tracker.getStore(), 
tracker.getSegmentVersion(), "c", gcGeneration),
+                new RecordCache<String>() {
+                    @Override
+                    protected Cache<String> getCache(int generation) {
+                        return cache;
+                    }
+                });
         this.cancel = cancel;
     }
 
-    @Nonnull
-    private static SegmentWriter createSegmentWriter(SegmentTracker tracker) {
-        return new SegmentWriter(tracker.getStore(), 
tracker.getSegmentVersion(),
-            new SegmentBufferWriter(tracker.getStore(), 
tracker.getSegmentVersion(), "c", tracker.getGcGen() + 1));
+    public int getGCGeneration() {
+        return gcGeneration;
     }
 
     /**

Modified: 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordCache.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordCache.java?rev=1740094&r1=1740093&r2=1740094&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordCache.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/RecordCache.java
 Wed Apr 20 10:04:46 2016
@@ -19,67 +19,189 @@
 
 package org.apache.jackrabbit.oak.plugins.segment;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newConcurrentMap;
+import static com.google.common.collect.Sets.newHashSet;
+
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * FIXME OAK-3348 XXX document
  */
-abstract class RecordCache<T> {
-    // FIXME OAK-3348 XXX this caches retain in mem refs to old gens. assess 
impact and mitigate/fix
-    private static final RecordCache<?> DISABLED_CACHE = new 
RecordCache<Object>() {
-        @Override
-        RecordId get(Object key) {
-            return null;
+public class RecordCache<T> {
+    private static final Logger LOG = 
LoggerFactory.getLogger(RecordCache.class);
+    private static final int RETENTION_THRESHOLD = 1;
+
+    private final ConcurrentMap<Integer, Cache<T>> generations = 
newConcurrentMap();
+
+    public abstract static class Cache<T> {
+        public static <T> Cache<T> disabled() {
+            return new Cache<T>() {
+                @Override void put(T key, RecordId value) { }
+                @Override void put(T key, RecordId value, int cost) { }
+                @Override RecordId get(T key) { return null; }
+                @Override void clear() { }
+            };
         }
+        abstract void put(T key, RecordId value);
+        abstract void put(T key, RecordId value, int cost);
+        abstract RecordId get(T key);
+        abstract void clear();
+    }
 
-        @Override
-        RecordId put(Object key, RecordId value) {
-            return null;
+    public static <T> RecordCache<T> disabled() {
+        return new RecordCache<T>() {
+            @Override public Cache<T> generation(int generation) { return 
Cache.disabled(); }
+            @Override public void clear(int generation) { }
+            @Override public void clear() { }
+        };
+    }
+
+    /**
+     */
+    protected Cache<T> getCache(int generation) {
+        return Cache.disabled();
+    }
+
+    public Cache<T> generation(int generation) {
+        // Avoid calling getCache on every invocation.
+        if (!generations.containsKey(generation)) {
+            // The small race here might still result in getCache being called
+            // more than once. Implementors much take this into account.
+            generations.putIfAbsent(generation, getCache(generation));
         }
+        return generations.get(generation);
+    }
 
-        @Override
-        void clear() { }
-    };
+    public void put(Cache<T> cache, int generation) {
+        generations.put(generation, cache);
+    }
 
-    @SuppressWarnings("unchecked")
-    static <T> RecordCache<T> newRecordCache(final int initialSize, final int 
size, boolean disabled) {
-        if (disabled) {
-            return (RecordCache<T>) DISABLED_CACHE;
-        } else {
-            return new LRURecordCache<T>(initialSize, size);
+    public void clearUpTo(int maxGen) {
+        Iterator<Integer> it = generations.keySet().iterator();
+        while (it.hasNext()) {
+            Integer gen =  it.next();
+            if (gen <= maxGen) {
+                it.remove();
+            }
         }
     }
 
-    abstract RecordId get(Object key);
-    abstract RecordId put(T key, RecordId value);
-    abstract void clear();
+    public void clear(int generation) {
+        generations.remove(generation);
+    }
+
+    public void clear() {
+        generations.clear();
+    }
 
-    private static final class LRURecordCache<T> extends RecordCache<T> {
-        private final Map<T, RecordId> cache;
+    public static final class LRUCache<T> extends Cache<T> {
+        private final Map<T, RecordId> map;
 
-        private LRURecordCache(int initialSize, final int size) {
-            cache = new LinkedHashMap<T, RecordId>(initialSize, 0.9f, true) {
+        public LRUCache(final int size) {
+            map = new LinkedHashMap<T, RecordId>(size * 4 / 3, 0.75f, true) {
                 @Override
                 protected boolean removeEldestEntry(Map.Entry<T, RecordId> 
eldest) {
-                    return size() > size;
+                    return size() >= size;
                 }
             };
         }
 
         @Override
-        synchronized RecordId get(Object key) {
-            return cache.get(key);
+        public synchronized void put(T key, RecordId value) {
+            map.put(key, value);
         }
 
         @Override
-        synchronized RecordId put(T key, RecordId value) {
-            return cache.put(key, value);
+        public void put(T key, RecordId value, int cost) {
+            throw new UnsupportedOperationException("Cannot put with a cost");
+        }
+
+        @Override
+        public synchronized RecordId get(T key) {
+            return map.get(key);
         }
 
         @Override
         public synchronized void clear() {
-            cache.clear();
+            map.clear();
         }
     }
+
+    public static final class DeduplicationCache<T> extends Cache<T> {
+        private final int capacity;
+        private final List<Map<T, RecordId>> maps;
+
+        private int size;
+
+        private final Set<Integer> muteDepths = newHashSet();
+
+        public DeduplicationCache(int capacity, int maxDepth) {
+            checkArgument(capacity > 0);
+            checkArgument(maxDepth > 0);
+            this.capacity = capacity;
+            this.maps = newArrayList();
+            for (int k = 0; k < maxDepth; k++) {
+                maps.add(new HashMap<T, RecordId>());
+            }
+        }
+
+        @Override
+        public void put(T key, RecordId value) {
+            throw new UnsupportedOperationException("Cannot put without a 
cost");
+        }
+
+        @Override
+        public synchronized void put(T key, RecordId value, int cost) {
+            while (size >= capacity) {
+                int d = maps.size() - 1;
+                int removed = maps.remove(d).size();
+                size -= removed;
+                if (removed > 0) {
+                    LOG.info("Evicted cache at depth {} as size {} reached 
capacity {}. " +
+                        "New size is {}", d, size + removed, capacity, size);
+                }
+            }
+
+            if (cost < maps.size()) {
+                if (maps.get(cost).put(key, value) == null) {
+                    size++;
+                }
+            } else {
+                if (muteDepths.add(cost)) {
+                    LOG.info("Not caching {} -> {} as depth {} reaches or 
exceeds the maximum of {}",
+                        key, value, cost, maps.size());
+                }
+            }
+        }
+
+        @Override
+        public synchronized RecordId get(T key) {
+            for (Map<T, RecordId> map : maps) {
+                if (!map.isEmpty()) {
+                    RecordId recordId = map.get(key);
+                    if (recordId != null) {
+                        return recordId;
+                    }
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public synchronized void clear() {
+            maps.clear();
+        }
+
+    }
 }

Modified: 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java?rev=1740094&r1=1740093&r2=1740094&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java
 Wed Apr 20 10:04:46 2016
@@ -40,7 +40,6 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.api.Type.NAMES;
 import static org.apache.jackrabbit.oak.api.Type.STRING;
 import static 
org.apache.jackrabbit.oak.plugins.segment.MapRecord.BUCKETS_PER_LEVEL;
-import static 
org.apache.jackrabbit.oak.plugins.segment.RecordCache.newRecordCache;
 import static 
org.apache.jackrabbit.oak.plugins.segment.RecordWriters.newBlobIdWriter;
 import static 
org.apache.jackrabbit.oak.plugins.segment.RecordWriters.newBlockWriter;
 import static 
org.apache.jackrabbit.oak.plugins.segment.RecordWriters.newListBucketWriter;
@@ -67,12 +66,12 @@ import java.util.concurrent.atomic.Atomi
 
 import javax.jcr.PropertyType;
 
-import com.google.common.base.Objects;
 import com.google.common.io.Closeables;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.memory.ModifiedNodeState;
+import org.apache.jackrabbit.oak.plugins.segment.RecordCache.Cache;
 import 
org.apache.jackrabbit.oak.plugins.segment.WriteOperationHandler.WriteOperation;
 import org.apache.jackrabbit.oak.spi.blob.BlobStore;
 import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
@@ -97,9 +96,15 @@ public class SegmentWriter {
      * Cache of recently stored string records, used to avoid storing 
duplicates
      * of frequently occurring data.
      */
-    private final RecordCache<Object> stringCache = newRecordCache(
-        STRING_RECORDS_CACHE_SIZE <= 0 ? 0 : (int) (STRING_RECORDS_CACHE_SIZE 
* 1.2),
-        STRING_RECORDS_CACHE_SIZE, STRING_RECORDS_CACHE_SIZE <= 0);
+    private final RecordCache<String> stringCache =
+        STRING_RECORDS_CACHE_SIZE <= 0
+            ? RecordCache.<String>disabled()
+            : new RecordCache<String>() {
+                @Override
+                protected Cache<String> getCache(int generation) {
+                    return new LRUCache<String>(STRING_RECORDS_CACHE_SIZE);
+                }
+        };
 
     private static final int TPL_RECORDS_CACHE_SIZE = Integer.getInteger(
             "oak.segment.writer.templatesCacheSize", 3000);
@@ -108,9 +113,17 @@ public class SegmentWriter {
      * Cache of recently stored template records, used to avoid storing
      * duplicates of frequently occurring data.
      */
-    private final RecordCache<Object> templateCache = newRecordCache(
-        TPL_RECORDS_CACHE_SIZE <= 0 ? 0 : (int) (TPL_RECORDS_CACHE_SIZE * 1.2),
-        TPL_RECORDS_CACHE_SIZE, TPL_RECORDS_CACHE_SIZE <= 0);
+    private final RecordCache<Template> templateCache =
+        TPL_RECORDS_CACHE_SIZE <= 0
+            ? RecordCache.<Template>disabled()
+            : new RecordCache<Template>() {
+            @Override
+            protected Cache<Template> getCache(int generation) {
+                return new LRUCache<Template>(TPL_RECORDS_CACHE_SIZE);
+            }
+        };
+
+    private final RecordCache<String> nodeCache;
 
 
     private final SegmentStore store;
@@ -122,14 +135,28 @@ public class SegmentWriter {
 
     private final WriteOperationHandler writeOperationHandler;
 
+    public SegmentWriter(SegmentStore store, SegmentVersion version, 
WriteOperationHandler writeOperationHandler,
+            RecordCache<String> nodeCache) {
+        this.store = store;
+        this.version = version;
+        this.writeOperationHandler = writeOperationHandler;
+        this.nodeCache = nodeCache;
+    }
+
     /**
      * @param store     store to write to
      * @param version   segment version to write
      */
     public SegmentWriter(SegmentStore store, SegmentVersion version, 
WriteOperationHandler writeOperationHandler) {
-        this.store = store;
-        this.version = version;
-        this.writeOperationHandler = writeOperationHandler;
+        this(store, version, writeOperationHandler, new RecordCache<String>());
+    }
+
+    public void addCachedNodes(int generation, Cache<String> cache) {
+        nodeCache.put(cache, generation);
+
+        stringCache.clearUpTo(generation - 1);
+        templateCache.clearUpTo(generation - 1);
+        nodeCache.clearUpTo(generation - 1);
     }
 
     public void flush() throws IOException {
@@ -224,7 +251,7 @@ public class SegmentWriter {
             writeOperationHandler.execute(new SegmentWriteOperation() {
                 @Override
                 public RecordId execute(SegmentBufferWriter writer) throws 
IOException {
-                    return with(writer).writeNode(state);
+                    return with(writer).writeNode(state, 0);
                 }
             }));
     }
@@ -242,6 +269,10 @@ public class SegmentWriter {
             return this;
         }
 
+        private int generation() {
+            return writer.getGeneration();
+        }
+
         private RecordId writeMap(MapRecord base, Map<String, RecordId> 
changes) throws IOException {
             if (base != null && base.isDiff()) {
                 Segment segment = base.getSegment();
@@ -467,7 +498,7 @@ public class SegmentWriter {
          * @return value record identifier
          */
         private RecordId writeString(String string) throws IOException {
-            RecordId id = stringCache.get(key(string));
+            RecordId id = stringCache.generation(generation()).get(string);
             if (id != null) {
                 return id; // shortcut if the same string was recently stored
             }
@@ -477,7 +508,7 @@ public class SegmentWriter {
             if (data.length < Segment.MEDIUM_LIMIT) {
                 // only cache short strings to avoid excessive memory use
                 id = writeValueRecord(data.length, data);
-                stringCache.put(key(string), id);
+                stringCache.generation(generation()).put(string, id);
                 return id;
             }
 
@@ -656,7 +687,7 @@ public class SegmentWriter {
         private RecordId writeTemplate(Template template) throws IOException {
             checkNotNull(template);
 
-            RecordId id = templateCache.get(key(template));
+            RecordId id = templateCache.generation(generation()).get(template);
             if (id != null) {
                 return id; // shortcut if the same template was recently stored
             }
@@ -727,22 +758,42 @@ public class SegmentWriter {
             RecordId tid = newTemplateWriter(ids, propertyNames,
                 propertyTypes, head, primaryId, mixinIds, childNameId,
                 propNamesId, version).write(writer);
-            templateCache.put(key(template), tid);
+            templateCache.generation(generation()).put(template, tid);
             return tid;
         }
 
-        // FIXME OAK-3348 defer compacted items are not in the compaction map 
-> performance regression
-        //        split compaction map into 1) id based equality and 2) cache 
(like string and template) for nodes
-        private RecordId writeNode(NodeState state) throws IOException {
+        private RecordId toCache(NodeState state, int depth, RecordId 
recordId) {
+            if (state instanceof SegmentNodeState) {
+                SegmentNodeState sns = (SegmentNodeState) state;
+                nodeCache.generation(generation()).put(sns.getId(), recordId, 
depth);
+            }
+            return recordId;
+        }
+
+        private RecordId writeNode(NodeState state, int depth) throws 
IOException {
             if (state instanceof SegmentNodeState) {
                 SegmentNodeState sns = ((SegmentNodeState) state);
                 if (hasSegment(sns)) {
-                    if (!isOldGen(sns.getRecordId())) {
+                    if (isOldGen(sns.getRecordId())) {
+                        RecordId cachedId = 
nodeCache.generation(generation()).get(sns.getId());
+                        if (cachedId != null) {
+                            return cachedId;
+                        }
+                    } else {
                         return sns.getRecordId();
                     }
                 }
             }
 
+            RecordId recordId = writeNodeUncached(state, depth);
+            if (state instanceof SegmentNodeState) {
+                SegmentNodeState sns = (SegmentNodeState) state;
+                nodeCache.generation(generation()).put(sns.getId(), recordId, 
depth);
+            }
+            return recordId;
+        }
+
+        private RecordId writeNodeUncached(NodeState state, int depth) throws 
IOException {
             SegmentNodeState before = null;
             Template beforeTemplate = null;
             ModifiedNodeState after = null;
@@ -782,19 +833,19 @@ public class SegmentWriter {
                     && before.getChildNodeCount(2) > 1
                     && after.getChildNodeCount(2) > 1) {
                     base = before.getChildNodeMap();
-                    childNodes = new ChildNodeCollectorDiff().diff(before, 
after);
+                    childNodes = new 
ChildNodeCollectorDiff(depth).diff(before, after);
                 } else {
                     base = null;
                     childNodes = newHashMap();
                     for (ChildNodeEntry entry : state.getChildNodeEntries()) {
                         childNodes.put(
                             entry.getName(),
-                            writeNode(entry.getNodeState()));
+                            writeNode(entry.getNodeState(), depth + 1));
                     }
                 }
                 ids.add(writeMap(base, childNodes));
             } else if (childName != Template.ZERO_CHILD_NODES) {
-                
ids.add(writeNode(state.getChildNode(template.getChildName())));
+                ids.add(writeNode(state.getChildNode(template.getChildName()), 
depth + 1));
             }
 
             List<RecordId> pIds = newArrayList();
@@ -855,14 +906,15 @@ public class SegmentWriter {
             return thatGen < thisGen;
         }
 
-        private <T> Key<T> key(T t) {
-            return new Key<T>(t, writer.getGeneration());
-        }
-
         private class ChildNodeCollectorDiff extends DefaultNodeStateDiff {
+            private final int depth;
             private final Map<String, RecordId> childNodes = newHashMap();
             private IOException exception;
 
+            private ChildNodeCollectorDiff(int depth) {
+                this.depth = depth;
+            }
+
             public Map<String, RecordId> diff(SegmentNodeState before, 
ModifiedNodeState after) throws IOException {
                 after.compareAgainstBaseState(before, this);
                 if (exception != null) {
@@ -875,7 +927,7 @@ public class SegmentWriter {
             @Override
             public boolean childNodeAdded(String name, NodeState after) {
                 try {
-                    childNodes.put(name, writeNode(after));
+                    childNodes.put(name, writeNode(after, depth + 1));
                 } catch (IOException e) {
                     exception = e;
                     return false;
@@ -887,7 +939,7 @@ public class SegmentWriter {
             public boolean childNodeChanged(
                 String name, NodeState before, NodeState after) {
                 try {
-                    childNodes.put(name, writeNode(after));
+                    childNodes.put(name, writeNode(after, depth + 1));
                 } catch (IOException e) {
                     exception = e;
                     return false;
@@ -912,32 +964,4 @@ public class SegmentWriter {
         return valueOf(NEXT_ID.getAndIncrement());
     }
 
-    private static final class Key<T> {
-        private final T t;
-        private final int generation;
-
-        private Key(T t, int generation) {
-            this.t = t;
-            this.generation = generation;
-        }
-
-        @Override
-        public boolean equals(Object other) {
-            if (this == other) {
-                return true;
-            }
-            if (other == null || getClass() != other.getClass()) {
-                return false;
-            }
-
-            Key<?> that = (Key<?>) other;
-            return generation == that.generation && t.equals(that.t);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hashCode(t, generation);
-        }
-    }
-
 }

Modified: 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java?rev=1740094&r1=1740093&r2=1740094&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-next/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java
 Wed Apr 20 10:04:46 2016
@@ -64,6 +64,7 @@ import com.google.common.base.Supplier;
 import org.apache.jackrabbit.oak.api.Blob;
 import org.apache.jackrabbit.oak.plugins.blob.BlobStoreBlob;
 import org.apache.jackrabbit.oak.plugins.segment.Compactor;
+import 
org.apache.jackrabbit.oak.plugins.segment.RecordCache.DeduplicationCache;
 import org.apache.jackrabbit.oak.plugins.segment.RecordId;
 import org.apache.jackrabbit.oak.plugins.segment.Segment;
 import 
org.apache.jackrabbit.oak.plugins.segment.SegmentGraph.SegmentGraphVisitor;
@@ -1010,7 +1011,8 @@ public class FileStore implements Segmen
         gcMonitor.info("TarMK GC #{}: compaction started, strategy={}", 
gcCount, compactionStrategy);
         Stopwatch watch = Stopwatch.createStarted();
         Supplier<Boolean> compactionCanceled = newCancelCompactionCondition();
-        Compactor compactor = new Compactor(tracker, compactionStrategy, 
compactionCanceled);
+        DeduplicationCache<String> nodeCache = new 
DeduplicationCache<String>(1000000, 20);
+        Compactor compactor = new Compactor(tracker, nodeCache, 
compactionCanceled);
         SegmentNodeState before = getHead();
         long existing = before.getChildNode(SegmentNodeStore.CHECKPOINTS)
                 .getChildNodeCount(Long.MAX_VALUE);
@@ -1051,6 +1053,7 @@ public class FileStore implements Segmen
                 }
             }
             if (success) {
+                
tracker.getWriter().addCachedNodes(compactor.getGCGeneration(), nodeCache);
                 tracker.clearSegmentIdTables(compactionStrategy);
                 gcMonitor.compacted(new long[0], new long[0], new long[0]);
             } else {

Modified: 
jackrabbit/oak/trunk/oak-segment-next/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-segment-next/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java?rev=1740094&r1=1740093&r2=1740094&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-segment-next/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-segment-next/src/test/java/org/apache/jackrabbit/oak/plugins/segment/CompactionAndCleanupIT.java
 Wed Apr 20 10:04:46 2016
@@ -64,9 +64,6 @@ import org.apache.jackrabbit.oak.spi.sta
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -538,6 +535,39 @@ public class CompactionAndCleanupIT {
         } finally {
             fileStore.close();
         }
+    }
+
+    @Test
+    public void checkpointDeduplicationTest() throws IOException, 
CommitFailedException {
+        FileStore fileStore = FileStore.builder(getFileStoreFolder()).build();
+        CompactionStrategy strategy = new CompactionStrategy(false, false, 
CLEAN_NONE, 0, (byte) 0);
+        fileStore.setCompactionStrategy(strategy);
+        try {
+            SegmentNodeStore nodeStore = 
SegmentNodeStore.builder(fileStore).build();
+            NodeBuilder builder = nodeStore.getRoot().builder();
+            builder.setChildNode("a").setChildNode("aa");
+            builder.setChildNode("b").setChildNode("bb");
+            builder.setChildNode("c").setChildNode("cc");
+            nodeStore.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+
+            String cpId = nodeStore.checkpoint(Long.MAX_VALUE);
+
+            NodeState uncompacted = nodeStore.getRoot();
+            fileStore.compact();
+            NodeState compacted = nodeStore.getRoot();
+
+            assertEquals(uncompacted, compacted);
+            assertTrue(uncompacted instanceof SegmentNodeState);
+            assertTrue(compacted instanceof SegmentNodeState);
+            assertEquals(((SegmentNodeState)uncompacted).getId(), 
((SegmentNodeState)compacted).getId());
+
+            NodeState checkpoint = nodeStore.retrieve(cpId);
+            assertTrue(checkpoint instanceof SegmentNodeState);
+            assertEquals("Checkpoint should get de-duplicated",
+                ((Record) compacted).getRecordId(), ((Record) 
checkpoint).getRecordId());
+        } finally {
+            fileStore.close();
+        }
     }
 
     private static void addContent(NodeBuilder builder) {


Reply via email to