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) {