Author: jukka
Date: Thu Dec 12 21:01:15 2013
New Revision: 1550530
URL: http://svn.apache.org/r1550530
Log:
OAK-593: Segment-based MK
Add a special "diff" map record type for more efficient handling of the
common case where just a single entry that already exists in the base map
gets modified.
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/MapRecord.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/MapRecord.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/MapRecord.java?rev=1550530&r1=1550529&r2=1550530&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/MapRecord.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/MapRecord.java
Thu Dec 12 21:01:15 2013
@@ -79,8 +79,17 @@ class MapRecord extends Record {
}
boolean isLeaf() {
- int head = getSegment().readInt(getOffset(0));
- return !isBranch(getSize(head), getLevel(head));
+ Segment segment = getSegment();
+ int head = segment.readInt(getOffset(0));
+ if (isDiff(head)) {
+ RecordId base = segment.readRecordId(getOffset(8, 2));
+ return new MapRecord(segment, base).isLeaf();
+ }
+ return !isBranch(head);
+ }
+
+ public boolean isDiff() {
+ return isDiff(getSegment().readInt(getOffset(0)));
}
MapRecord[] getBuckets() {
@@ -114,20 +123,37 @@ class MapRecord extends Record {
int size() {
Segment segment = getSegment();
- return getSize(segment.readInt(getOffset(0)));
+ int head = segment.readInt(getOffset(0));
+ if (isDiff(head)) {
+ RecordId base = segment.readRecordId(getOffset(8, 2));
+ return new MapRecord(segment, base).size();
+ }
+ return getSize(head);
}
- MapEntry getEntry(String key) {
- checkNotNull(key);
+ MapEntry getEntry(String name) {
+ checkNotNull(name);
+ int hash = getHash(name);
Segment segment = getSegment();
int head = segment.readInt(getOffset(0));
+ if (isDiff(head)) {
+ if (hash == segment.readInt(getOffset(4))) {
+ RecordId key = segment.readRecordId(getOffset(8));
+ if (name.equals(segment.readString(key))) {
+ RecordId value = segment.readRecordId(getOffset(8, 1));
+ return new MapEntry(segment, name, key, value);
+ }
+ }
+ RecordId base = segment.readRecordId(getOffset(8, 2));
+ return new MapRecord(segment, base).getEntry(name);
+ }
+
int size = getSize(head);
if (size == 0) {
return null; // shortcut
}
- int hash = getHash(key);
int level = getLevel(head);
if (isBranch(size, level)) {
// this is an intermediate branch record
@@ -140,7 +166,7 @@ class MapRecord extends Record {
if ((bitmap & bit) != 0) {
int ids = bitCount(bitmap & (bit - 1));
RecordId id = segment.readRecordId(getOffset(8, ids));
- return new MapRecord(segment, id).getEntry(key);
+ return new MapRecord(segment, id).getEntry(name);
} else {
return null;
}
@@ -161,11 +187,11 @@ class MapRecord extends Record {
if (diff == 0) {
RecordId keyId = segment.readRecordId(
getOffset(4 + size * 4, i * 2));
- diff = segment.readString(keyId).compareTo(key);
+ diff = segment.readString(keyId).compareTo(name);
if (diff == 0) {
RecordId valueId = segment.readRecordId(
getOffset(4 + size * 4, i * 2 + 1));
- return new MapEntry(segment, key, keyId, valueId);
+ return new MapEntry(segment, name, keyId, valueId);
}
}
if (diff < 0) {
@@ -179,10 +205,70 @@ class MapRecord extends Record {
return null;
}
+ private RecordId getValue(int hash, RecordId key) {
+ checkNotNull(key);
+ Segment segment = getSegment();
+
+ int head = segment.readInt(getOffset(0));
+ if (isDiff(head)) {
+ if (hash == segment.readInt(getOffset(4))
+ && key.equals(segment.readRecordId(getOffset(8)))) {
+ return segment.readRecordId(getOffset(8, 1));
+ }
+ RecordId base = segment.readRecordId(getOffset(8, 2));
+ return new MapRecord(segment, base).getValue(hash, key);
+ }
+
+ int size = getSize(head);
+ if (size == 0) {
+ return null; // shortcut
+ }
+
+ int level = getLevel(head);
+ if (isBranch(size, level)) {
+ // this is an intermediate branch record
+ // check if a matching bucket exists, and recurse
+ int bitmap = segment.readInt(getOffset(4));
+ int mask = (1 << BITS_PER_LEVEL) - 1;
+ int shift = 32 - (level + 1) * BITS_PER_LEVEL;
+ int index = (hash >> shift) & mask;
+ int bit = 1 << index;
+ if ((bitmap & bit) != 0) {
+ int ids = bitCount(bitmap & (bit - 1));
+ RecordId id = segment.readRecordId(getOffset(8, ids));
+ return new MapRecord(segment, id).getValue(hash, key);
+ } else {
+ return null;
+ }
+ }
+
+ // this is a leaf record; scan the list to find a matching entry
+ Long h = hash & HASH_MASK;
+ for (int i = 0; i < size; i++) {
+ int hashOffset = getOffset(4 + i * 4);
+ int diff = h.compareTo(segment.readInt(hashOffset) & HASH_MASK);
+ if (diff > 0) {
+ return null;
+ } else if (diff == 0) {
+ int keyOffset = getOffset(4 + size * 4, i * 2);
+ if (key.equals(segment.readRecordId(keyOffset))) {
+ int valueOffset = getOffset(4 + size * 4, i * 2 + 1);
+ return segment.readRecordId(valueOffset);
+ }
+ }
+ }
+ return null;
+ }
+
Iterable<String> getKeys() {
Segment segment = getSegment();
int head = segment.readInt(getOffset(0));
+ if (isDiff(head)) {
+ RecordId base = segment.readRecordId(getOffset(8, 2));
+ return new MapRecord(segment, base).getKeys();
+ }
+
int size = getSize(head);
if (size == 0) {
return Collections.emptyList(); // shortcut
@@ -212,9 +298,21 @@ class MapRecord extends Record {
}
Iterable<MapEntry> getEntries() {
+ return getEntries(null, null);
+ }
+
+ private Iterable<MapEntry> getEntries(
+ RecordId diffKey, RecordId diffValue) {
Segment segment = getSegment();
int head = segment.readInt(getOffset(0));
+ if (isDiff(head)) {
+ RecordId key = segment.readRecordId(getOffset(8));
+ RecordId value = segment.readRecordId(getOffset(8, 1));
+ RecordId base = segment.readRecordId(getOffset(8, 2));
+ return new MapRecord(segment, base).getEntries(key, value);
+ }
+
int size = getSize(head);
if (size == 0) {
return Collections.emptyList(); // shortcut
@@ -226,7 +324,7 @@ class MapRecord extends Record {
List<Iterable<MapEntry>> entries =
newArrayListWithCapacity(buckets.size());
for (MapRecord bucket : buckets) {
- entries.add(bucket.getEntries());
+ entries.add(bucket.getEntries(diffKey, diffValue));
}
return concat(entries);
}
@@ -235,7 +333,11 @@ class MapRecord extends Record {
RecordId[] values = new RecordId[size];
for (int i = 0; i < size; i++) {
keys[i] = segment.readRecordId(getOffset(4 + size * 4, i * 2));
- values[i] = segment.readRecordId(getOffset(4 + size * 4, i * 2 +
1));
+ if (keys[i].equals(diffKey)) {
+ values[i] = diffValue;
+ } else {
+ values[i] = segment.readRecordId(getOffset(4 + size * 4, i * 2
+ 1));
+ }
}
MapEntry[] entries = new MapEntry[size];
@@ -296,7 +398,48 @@ class MapRecord extends Record {
Segment afterSegment = after.getSegment();
int afterHead = afterSegment.readInt(after.getOffset(0));
- if (isBranch(beforeHead) && isBranch(afterHead)) {
+ if (isDiff(afterHead)) {
+ RecordId base = afterSegment.readRecordId(after.getOffset(8, 2));
+ if (base.equals(after.getRecordId())) {
+ int hash = afterSegment.readInt(after.getOffset(4));
+ RecordId key = afterSegment.readRecordId(after.getOffset(8));
+ RecordId afterValue =
afterSegment.readRecordId(after.getOffset(8, 1));
+ RecordId beforeValue = before.getValue(hash, key);
+ String name = beforeSegment.readString(key);
+ return diff.entryChanged(
+ new MapEntry(beforeSegment, name, key, beforeValue),
+ new MapEntry(afterSegment, name, key, afterValue));
+ } else if (isDiff(beforeHead)) {
+ RecordId beforeBase =
+ beforeSegment.readRecordId(before.getOffset(8, 2));
+ if (base.equals(beforeBase)) {
+ int beforeHash =
beforeSegment.readInt(before.getOffset(4));
+ RecordId beforeKey =
beforeSegment.readRecordId(before.getOffset(8));
+ RecordId beforeValue =
beforeSegment.readRecordId(before.getOffset(8, 1));
+
+ int afterHash = afterSegment.readInt(after.getOffset(4));
+ RecordId afterKey =
afterSegment.readRecordId(after.getOffset(8));
+ RecordId afterValue =
afterSegment.readRecordId(after.getOffset(8, 1));
+
+ if (beforeKey.equals(afterKey)) {
+ String name = beforeSegment.readString(beforeKey);
+ return diff.entryChanged(
+ new MapEntry(beforeSegment, name, beforeKey,
beforeValue),
+ new MapEntry(afterSegment, name, afterKey,
afterValue));
+ } else {
+ String beforeName =
beforeSegment.readString(beforeKey);
+ String afterName = afterSegment.readString(afterKey);
+ return diff.entryChanged(
+ new MapEntry(beforeSegment, beforeName,
beforeKey, beforeValue),
+ new MapEntry(afterSegment, beforeName,
beforeKey, after.getValue(beforeHash, beforeKey)))
+ &&
+ diff.entryChanged(
+ new MapEntry(beforeSegment, afterName,
afterKey, before.getValue(afterHash, afterKey)),
+ new MapEntry(afterSegment, afterName,
afterKey, afterValue));
+ }
+ }
+ }
+ } else if (isBranch(beforeHead) && isBranch(afterHead)) {
return compareBranch(before, after, diff);
}
@@ -377,6 +520,10 @@ class MapRecord extends Record {
return head >>> MapRecord.SIZE_BITS;
}
+ private static boolean isDiff(int head) {
+ return head == -1;
+ }
+
private static boolean isBranch(int head) {
return isBranch(getSize(head), getLevel(head));
}
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java?rev=1550530&r1=1550529&r2=1550530&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java
(original)
+++
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java
Thu Dec 12 21:01:15 2013
@@ -29,6 +29,7 @@ import static com.google.common.collect.
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Maps.newLinkedHashMap;
import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.nCopies;
import static org.apache.jackrabbit.oak.api.Type.NAME;
@@ -523,6 +524,42 @@ public class SegmentWriter {
}
public MapRecord writeMap(MapRecord base, Map<String, RecordId> changes) {
+ if (base != null && base.isDiff()) {
+ Segment segment = base.getSegment();
+ RecordId key = segment.readRecordId(base.getOffset(8));
+ String name = segment.readString(key);
+ if (!changes.containsKey(name)) {
+ changes.put(name, segment.readRecordId(base.getOffset(8, 1)));
+ }
+ base = new MapRecord(
+ segment, segment.readRecordId(base.getOffset(8, 2)));
+ }
+
+ if (base != null && changes.size() == 1) {
+ Map.Entry<String, RecordId> change =
+ changes.entrySet().iterator().next();
+ RecordId value = change.getValue();
+ if (value != null) {
+ MapEntry entry = base.getEntry(change.getKey());
+ if (entry != null) {
+ if (value.equals(entry.getValue())) {
+ return base;
+ } else {
+ synchronized (this) {
+ RecordId id = prepare(RecordType.BRANCH, 8, asList(
+ entry.getKey(), value,
base.getRecordId()));
+ writeInt(-1);
+ writeInt(entry.getHash());
+ writeRecordId(entry.getKey());
+ writeRecordId(value);
+ writeRecordId(base.getRecordId());
+ return new MapRecord(dummySegment, id);
+ }
+ }
+ }
+ }
+ }
+
List<MapEntry> entries = Lists.newArrayList();
for (Map.Entry<String, RecordId> entry : changes.entrySet()) {
String key = entry.getKey();