提交 337f0a4f authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: support for concurrent reads and writes is now enabled by default.

上级 a17b66d1
......@@ -8,7 +8,6 @@ package org.h2.mvstore;
import java.util.Arrays;
import java.util.Iterator;
/**
* A very simple array list that supports concurrent access.
* Internally, it uses immutable objects.
......
......@@ -21,6 +21,15 @@ import org.h2.util.New;
/**
* A stored map.
* <p>
* Read operations can happen concurrently with all other
* operations, without risk of corruption.
* <p>
* Write operations first read the relevant area from disk to memory
* concurrently, and only then modify the data. The in-memory part of write
* operations is synchronized. For scalable concurrent in-memory write
* operations, the map should be split into multiple smaller sub-maps that are
* then synchronized independently.
*
* @param <K> the key class
* @param <V> the value class
......@@ -43,11 +52,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/
protected volatile long writeVersion;
/**
* This version is set during a write operation.
*/
protected volatile long currentWriteVersion = -1;
private int id;
private long createVersion;
private final DataType keyType;
......@@ -99,21 +103,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
this.writeVersion = store.getCurrentVersion();
}
/**
* Create a copy of a page, if the write version is higher than the current
* version. If a copy is created, the old page is marked as deleted.
*
* @param p the page
* @param writeVersion the write version
* @return a page with the given write version
*/
protected Page copyOnWrite(Page p, long writeVersion) {
if (p.getVersion() == writeVersion) {
return p;
}
return p.copy(writeVersion);
}
/**
* Add or replace a key-value pair.
*
......@@ -126,16 +115,12 @@ public class MVMap<K, V> extends AbstractMap<K, V>
public synchronized V put(K key, V value) {
DataUtils.checkArgument(value != null, "The value may not be null");
beforeWrite();
try {
long v = writeVersion;
Page p = copyOnWrite(root, v);
p = splitRootIfNeeded(p, v);
Object result = put(p, v, key, value);
newRoot(p);
return (V) result;
} finally {
afterWrite();
}
long v = writeVersion;
Page p = root.copy(v);
p = splitRootIfNeeded(p, v);
Object result = put(p, v, key, value);
newRoot(p);
return (V) result;
}
/**
......@@ -155,14 +140,13 @@ public class MVMap<K, V> extends AbstractMap<K, V>
Page split = p.split(at);
Object[] keys = { k };
Page.PageReference[] children = {
new Page.PageReference(p, p.getPos()),
new Page.PageReference(split, split.getPos()),
new Page.PageReference(p, p.getPos(), p.getTotalCount()),
new Page.PageReference(split, split.getPos(), split.getTotalCount()),
};
long[] counts = { p.getTotalCount(), split.getTotalCount() };
p = Page.create(this, writeVersion,
1, keys, null,
2, children, counts,
totalCount, 0, 0);
keys, null,
children,
totalCount, 0);
return p;
}
......@@ -191,21 +175,19 @@ public class MVMap<K, V> extends AbstractMap<K, V>
} else {
index++;
}
Page c = copyOnWrite(p.getChildPage(index), writeVersion);
Page c = p.getChildPage(index).copy(writeVersion);
if (c.getMemory() > store.getPageSplitSize() && c.getKeyCount() > 1) {
// split on the way down
int at = c.getKeyCount() / 2;
Object k = c.getKey(at);
Page split = c.split(at);
p.setChild(index, split);
p.setCounts(index, split);
p.insertNode(index, k, c);
// now we are not sure where to add
return put(p, writeVersion, key, value);
}
p.setChild(index, c);
Object result = put(c, writeVersion, key, value);
p.setCounts(index, c);
p.setChild(index, c);
return result;
}
......@@ -510,12 +492,8 @@ public class MVMap<K, V> extends AbstractMap<K, V>
@Override
public synchronized void clear() {
beforeWrite();
try {
root.removeAllRecursive();
newRoot(Page.createEmpty(this, writeVersion));
} finally {
afterWrite();
}
root.removeAllRecursive();
newRoot(Page.createEmpty(this, writeVersion));
}
/**
......@@ -537,22 +515,24 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* @return the old value if the key existed, or null otherwise
*/
@Override
public synchronized V remove(Object key) {
@SuppressWarnings("unchecked")
public V remove(Object key) {
beforeWrite();
try {
long v = writeVersion;
Page p = copyOnWrite(root, v);
@SuppressWarnings("unchecked")
V result = (V) remove(p, v, key);
V result = get(key);
if (result == null) {
return null;
}
long v = writeVersion;
synchronized (this) {
Page p = root.copy(v);
result = (V) remove(p, v, key);
if (!p.isLeaf() && p.getTotalCount() == 0) {
p.removePage();
p = Page.createEmpty(this, p.getVersion());
}
newRoot(p);
return result;
} finally {
afterWrite();
}
return result;
}
/**
......@@ -664,18 +644,16 @@ public class MVMap<K, V> extends AbstractMap<K, V>
index++;
}
Page cOld = p.getChildPage(index);
Page c = copyOnWrite(cOld, writeVersion);
Page c = cOld.copy(writeVersion);
result = remove(c, writeVersion, key);
if (result == null || c.getTotalCount() != 0) {
// no change, or
// there are more nodes
p.setChild(index, c);
p.setCounts(index, c);
} else {
// this child was deleted
if (p.getKeyCount() == 0) {
p.setChild(index, c);
p.setCounts(index, c);
c.removePage();
} else {
p.remove(index);
......@@ -975,25 +953,21 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/
void rollbackTo(long version) {
beforeWrite();
try {
if (version <= createVersion) {
// the map is removed later
} else if (root.getVersion() >= version) {
while (true) {
Page last = oldRoots.peekLast();
if (last == null) {
break;
}
// slow, but rollback is not a common operation
oldRoots.removeLast(last);
root = last;
if (root.getVersion() < version) {
break;
}
if (version <= createVersion) {
// the map is removed later
} else if (root.getVersion() >= version) {
while (true) {
Page last = oldRoots.peekLast();
if (last == null) {
break;
}
// slow, but rollback is not a common operation
oldRoots.removeLast(last);
root = last;
if (root.getVersion() < version) {
break;
}
}
} finally {
afterWrite();
}
}
......@@ -1056,45 +1030,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
throw DataUtils.newUnsupportedOperationException(
"This map is read-only");
}
checkConcurrentWrite();
store.beforeWrite();
currentWriteVersion = writeVersion;
}
/**
* Check that no write operation is in progress.
*/
protected void checkConcurrentWrite() {
if (currentWriteVersion != -1) {
// try to detect concurrent modification
// on a best-effort basis
throw DataUtils.newConcurrentModificationException(getName());
}
}
/**
* This method is called after writing to the map (whether or not the write
* operation was successful).
*/
protected void afterWrite() {
currentWriteVersion = -1;
}
/**
* If there is a concurrent update to the given version, wait until it is
* finished.
*
* @param version the read version
*/
protected void waitUntilWritten(long version) {
if (readOnly) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Waiting for writes to a read-only map");
}
while (currentWriteVersion == version) {
Thread.yield();
}
}
@Override
......@@ -1267,7 +1203,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
void copyFrom(MVMap<K, V> sourceMap) {
beforeWrite();
newRoot(copy(sourceMap.root, null));
afterWrite();
}
private Page copy(Page source, CursorPos parent) {
......@@ -1276,11 +1211,10 @@ public class MVMap<K, V> extends AbstractMap<K, V>
Page child = target;
for (CursorPos p = parent; p != null; p = p.parent) {
p.page.setChild(p.index, child);
p.page = copyOnWrite(p.page, writeVersion);
p.page = p.page.copy(writeVersion);
child = p.page;
if (p.parent == null) {
newRoot(p.page);
afterWrite();
beforeWrite();
}
}
......
......@@ -9,17 +9,10 @@ import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.ObjectDataType;
/**
* A stored map. Read operations can happen concurrently with all other
* operations, without risk of corruption.
* <p>
* Write operations first read the relevant area from disk to memory
* concurrently, and only then modify the data. The in-memory part of write
* operations is synchronized. For scalable concurrent in-memory write
* operations, the map should be split into multiple smaller sub-maps that are
* then synchronized independently.
* A class used for backward compatibility.
*
* @param <K> the key class
* @param <V> the value class
* @param <K> the key type
* @param <V> the value type
*/
public class MVMapConcurrent<K, V> extends MVMap<K, V> {
......@@ -27,67 +20,6 @@ public class MVMapConcurrent<K, V> extends MVMap<K, V> {
super(keyType, valueType);
}
@Override
protected Page copyOnWrite(Page p, long writeVersion) {
return p.copy(writeVersion);
}
@Override
protected void checkConcurrentWrite() {
// ignore (writes are synchronized)
}
@Override
@SuppressWarnings("unchecked")
public V put(K key, V value) {
beforeWrite();
try {
// even if the result is the same, we still update the value
// (otherwise compact doesn't work)
get(key);
long v = writeVersion;
synchronized (this) {
Page p = copyOnWrite(root, v);
p = splitRootIfNeeded(p, v);
V result = (V) put(p, v, key, value);
newRoot(p);
return result;
}
} finally {
afterWrite();
}
}
@Override
protected void waitUntilWritten(long version) {
// no need to wait
}
@Override
@SuppressWarnings("unchecked")
public V remove(Object key) {
beforeWrite();
try {
V result = get(key);
if (result == null) {
return null;
}
long v = writeVersion;
synchronized (this) {
Page p = copyOnWrite(root, v);
result = (V) remove(p, v, key);
if (!p.isLeaf() && p.getTotalCount() == 0) {
p.removePage();
p = Page.createEmpty(this, p.getVersion());
}
newRoot(p);
}
return result;
} finally {
afterWrite();
}
}
/**
* A builder for this class.
*
......
......@@ -190,7 +190,7 @@ public class MVStore {
* The metadata map. Write access to this map needs to be synchronized on
* the store.
*/
private MVMapConcurrent<String, String> meta;
private MVMap<String, String> meta;
private final ConcurrentHashMap<Integer, MVMap<?, ?>> maps =
new ConcurrentHashMap<Integer, MVMap<?, ?>>();
......@@ -283,7 +283,7 @@ public class MVStore {
}
o = config.get("backgroundExceptionHandler");
this.backgroundExceptionHandler = (UncaughtExceptionHandler) o;
meta = new MVMapConcurrent<String, String>(StringDataType.INSTANCE,
meta = new MVMap<String, String>(StringDataType.INSTANCE,
StringDataType.INSTANCE);
HashMap<String, Object> c = New.hashMap();
c.put("id", 0);
......@@ -1010,7 +1010,6 @@ public class MVStore {
continue;
}
if (v >= 0 && v >= lastStoredVersion) {
m.waitUntilWritten(storeVersion);
MVMap<?, ?> r = m.openVersion(storeVersion);
if (r.getRoot().getPos() == 0) {
changed.add(r);
......
......@@ -29,10 +29,10 @@ import org.h2.mvstore.type.DataType;
*/
public class Page {
private static final int SHARED_KEYS = 1, SHARED_VALUES = 2,
SHARED_CHILDREN = 4, SHARED_COUNTS = 8;
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
/**
* An empty object array.
*/
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
private final MVMap<?, ?> map;
private long version;
......@@ -43,26 +43,11 @@ public class Page {
*/
private long totalCount;
/**
* The number of keys.
*/
private int keyCount;
/**
* The number of children.
*/
private int childCount;
/**
* The last result of a find operation is cached.
*/
private int cachedCompare;
/**
* Which arrays are shared with another version of this page.
*/
private int sharedFlags;
/**
* The estimated memory used.
*/
......@@ -89,13 +74,6 @@ public class Page {
*/
private PageReference[] children;
/**
* The descendant count for each child page.
* <p>
* The array might be larger than needed, to avoid frequent re-sizing.
*/
private long[] counts;
/**
* Whether the page is an in-memory (not stored, or not yet stored) page,
* and it is removed. This is to keep track of pages that concurrently
......@@ -118,9 +96,9 @@ public class Page {
*/
static Page createEmpty(MVMap<?, ?> map, long version) {
return create(map, version,
0, EMPTY_OBJECT_ARRAY, EMPTY_OBJECT_ARRAY,
0, null, null,
0, 0, DataUtils.PAGE_MEMORY);
EMPTY_OBJECT_ARRAY, EMPTY_OBJECT_ARRAY,
null,
0, DataUtils.PAGE_MEMORY);
}
/**
......@@ -128,31 +106,22 @@ public class Page {
*
* @param map the map
* @param version the version
* @param keyCount the number of keys
* @param keys the keys
* @param values the values
* @param childCount the number of children
* @param children the child page positions
* @param counts the children counts
* @param totalCount the total number of keys
* @param sharedFlags which arrays are shared
* @param memory the memory used in bytes
* @return the page
*/
public static Page create(MVMap<?, ?> map, long version, int keyCount,
Object[] keys, Object[] values, int childCount, PageReference[] children,
long[] counts, long totalCount,
int sharedFlags, int memory) {
public static Page create(MVMap<?, ?> map, long version,
Object[] keys, Object[] values, PageReference[] children,
long totalCount, int memory) {
Page p = new Page(map, version);
// the position is 0
p.keyCount = keyCount;
p.keys = keys;
p.values = values;
p.childCount = childCount;
p.children = children;
p.counts = counts;
p.totalCount = totalCount;
p.sharedFlags = sharedFlags;
if (memory == 0) {
p.recalculateMemory();
} else {
......@@ -176,17 +145,10 @@ public class Page {
public static Page create(MVMap<?, ?> map, long version, Page source) {
Page p = new Page(map, version);
// the position is 0
p.keyCount = source.keyCount;
p.keys = source.keys;
if (source.isLeaf()) {
p.values = source.values;
} else {
p.childCount = source.childCount;
p.children = source.children;
p.counts = source.counts;
}
p.values = source.values;
p.children = source.children;
p.totalCount = source.totalCount;
p.sharedFlags = source.sharedFlags;
p.memory = source.memory;
MVStore store = map.store;
if (store != null) {
......@@ -296,7 +258,7 @@ public class Page {
* @return the number of keys
*/
public int getKeyCount() {
return keyCount;
return keys.length;
}
/**
......@@ -327,14 +289,14 @@ public class Page {
int chunkId = DataUtils.getPageChunkId(pos);
buff.append("chunk: ").append(Long.toHexString(chunkId)).append("\n");
}
for (int i = 0; i <= keyCount; i++) {
for (int i = 0; i <= keys.length; i++) {
if (i > 0) {
buff.append(" ");
}
if (children != null) {
buff.append("[" + Long.toHexString(children[i].pos) + "] ");
}
if (i < keyCount) {
if (i < keys.length) {
buff.append(keys[i]);
if (values != null) {
buff.append(':');
......@@ -353,9 +315,8 @@ public class Page {
*/
public Page copy(long version) {
Page newPage = create(map, version,
keyCount, keys, values,
childCount, children, counts, totalCount,
SHARED_KEYS | SHARED_VALUES | SHARED_CHILDREN | SHARED_COUNTS,
keys, values,
children, totalCount,
getMemory());
// mark the old as deleted
removePage();
......@@ -375,7 +336,7 @@ public class Page {
* @return the value or null
*/
public int binarySearch(Object key) {
int low = 0, high = keyCount - 1;
int low = 0, high = keys.length - 1;
// the cached index minus one, so that
// for the first time (when cachedCompare is 0),
// the default value is used
......@@ -400,7 +361,7 @@ public class Page {
return -(low + 1);
// regular binary search (without caching)
// int low = 0, high = keyCount - 1;
// int low = 0, high = keys.length - 1;
// while (low <= high) {
// int x = (low + high) >>> 1;
// int compare = map.compare(key, keys[x]);
......@@ -426,39 +387,36 @@ public class Page {
}
private Page splitLeaf(int at) {
int a = at, b = keyCount - a;
int a = at, b = keys.length - a;
Object[] aKeys = new Object[a];
Object[] bKeys = new Object[b];
System.arraycopy(keys, 0, aKeys, 0, a);
System.arraycopy(keys, a, bKeys, 0, b);
keys = aKeys;
keyCount = a;
Object[] aValues = new Object[a];
Object[] bValues = new Object[b];
bValues = new Object[b];
System.arraycopy(values, 0, aValues, 0, a);
System.arraycopy(values, a, bValues, 0, b);
values = aValues;
sharedFlags &= ~(SHARED_KEYS | SHARED_VALUES);
totalCount = a;
Page newPage = create(map, version,
b, bKeys, bValues,
0, null, null,
bKeys.length, 0, 0);
bKeys, bValues,
null,
bKeys.length, 0);
recalculateMemory();
newPage.recalculateMemory();
return newPage;
}
private Page splitNode(int at) {
int a = at, b = keyCount - a;
int a = at, b = keys.length - a;
Object[] aKeys = new Object[a];
Object[] bKeys = new Object[b - 1];
System.arraycopy(keys, 0, aKeys, 0, a);
System.arraycopy(keys, a + 1, bKeys, 0, b - 1);
keys = aKeys;
keyCount = a;
PageReference[] aChildren = new PageReference[a + 1];
PageReference[] bChildren = new PageReference[b];
......@@ -466,27 +424,19 @@ public class Page {
System.arraycopy(children, a + 1, bChildren, 0, b);
children = aChildren;
long[] aCounts = new long[a + 1];
long[] bCounts = new long[b];
System.arraycopy(counts, 0, aCounts, 0, a + 1);
System.arraycopy(counts, a + 1, bCounts, 0, b);
counts = aCounts;
childCount = a + 1;
sharedFlags &= ~(SHARED_KEYS | SHARED_CHILDREN | SHARED_COUNTS);
long t = 0;
for (long x : aCounts) {
t += x;
for (PageReference x : aChildren) {
t += x.count;
}
totalCount = t;
t = 0;
for (long x : bCounts) {
t += x;
for (PageReference x : bChildren) {
t += x.count;
}
Page newPage = create(map, version,
b - 1, bKeys, null,
b, bChildren, bCounts,
t, 0, 0);
bKeys, null,
bChildren,
t, 0);
recalculateMemory();
newPage.recalculateMemory();
return newPage;
......@@ -501,10 +451,10 @@ public class Page {
if (MVStore.ASSERT) {
long check = 0;
if (isLeaf()) {
check = keyCount;
check = keys.length;
} else {
for (long x : counts) {
check += x;
for (PageReference x : children) {
check += x.count;
}
}
if (check != totalCount) {
......@@ -523,7 +473,7 @@ public class Page {
* @return the descendant count
*/
long getCounts(int index) {
return counts[index];
return children[index].count;
}
/**
......@@ -534,40 +484,11 @@ public class Page {
*/
public void setChild(int index, Page c) {
if (c != children[index].page || c.getPos() != children[index].pos) {
if ((sharedFlags & SHARED_CHILDREN) != 0) {
children = Arrays.copyOf(children, children.length);
sharedFlags &= ~SHARED_CHILDREN;
}
PageReference ref = new PageReference(c, c.pos);
long oldCount = children[index].count;
children = Arrays.copyOf(children, children.length);
PageReference ref = new PageReference(c, c.pos, c.totalCount);
children[index] = ref;
}
}
/**
* Update the (descendant) count for the given child, if there was a change.
*
* @param index the index
* @param c the new child page
*/
public void setCounts(int index, Page c) {
setCounts(index, c.totalCount);
}
/**
* Update the (descendant) count for the given child, if there was a change.
*
* @param index the index
* @param total the new value
*/
private void setCounts(int index, long total) {
if (total != counts[index]) {
if ((sharedFlags & SHARED_COUNTS) != 0) {
counts = Arrays.copyOf(counts, counts.length);
sharedFlags &= ~SHARED_COUNTS;
}
long oldCount = counts[index];
counts[index] = total;
totalCount += counts[index] - oldCount;
totalCount += c.totalCount - oldCount;
}
}
......@@ -578,10 +499,7 @@ public class Page {
* @param key the new key
*/
public void setKey(int index, Object key) {
if ((sharedFlags & SHARED_KEYS) != 0) {
keys = Arrays.copyOf(keys, keys.length);
sharedFlags &= ~SHARED_KEYS;
}
keys = Arrays.copyOf(keys, keys.length);
Object old = keys[index];
DataType keyType = map.getKeyType();
int mem = keyType.getMemory(key);
......@@ -601,10 +519,7 @@ public class Page {
*/
public Object setValue(int index, Object value) {
Object old = values[index];
if ((sharedFlags & SHARED_VALUES) != 0) {
values = Arrays.copyOf(values, values.length);
sharedFlags &= ~SHARED_VALUES;
}
values = Arrays.copyOf(values, values.length);
DataType valueType = map.getValueType();
addMemory(valueType.getMemory(value) -
valueType.getMemory(old));
......@@ -644,26 +559,15 @@ public class Page {
* @param value the value
*/
public void insertLeaf(int index, Object key, Object value) {
if (((sharedFlags & SHARED_KEYS) == 0) && keys.length > keyCount + 1) {
if (index < keyCount) {
System.arraycopy(keys, index, keys, index + 1,
keyCount - index);
System.arraycopy(values, index, values, index + 1,
keyCount - index);
}
} else {
int len = keyCount + 6;
Object[] newKeys = new Object[len];
DataUtils.copyWithGap(keys, newKeys, keyCount, index);
keys = newKeys;
Object[] newValues = new Object[len];
DataUtils.copyWithGap(values, newValues, keyCount, index);
values = newValues;
}
int len = keys.length + 1;
Object[] newKeys = new Object[len];
DataUtils.copyWithGap(keys, newKeys, len - 1, index);
keys = newKeys;
Object[] newValues = new Object[len];
DataUtils.copyWithGap(values, newValues, len - 1, index);
values = newValues;
keys[index] = key;
values[index] = value;
keyCount++;
sharedFlags &= ~(SHARED_KEYS | SHARED_VALUES);
totalCount++;
addMemory(map.getKeyType().getMemory(key) +
map.getValueType().getMemory(value));
......@@ -678,26 +582,18 @@ public class Page {
*/
public void insertNode(int index, Object key, Page childPage) {
Object[] newKeys = new Object[keyCount + 1];
DataUtils.copyWithGap(keys, newKeys, keyCount, index);
Object[] newKeys = new Object[keys.length + 1];
DataUtils.copyWithGap(keys, newKeys, keys.length, index);
newKeys[index] = key;
keys = newKeys;
keyCount++;
int childCount = children.length;
PageReference[] newChildren = new PageReference[childCount + 1];
DataUtils.copyWithGap(children, newChildren, childCount, index);
newChildren[index] = new PageReference(childPage, childPage.getPos());
newChildren[index] = new PageReference(
childPage, childPage.getPos(), childPage.totalCount);
children = newChildren;
long[] newCounts = new long[childCount + 1];
DataUtils.copyWithGap(counts, newCounts, childCount, index);
newCounts[index] = childPage.totalCount;
counts = newCounts;
childCount++;
sharedFlags &= ~(SHARED_KEYS | SHARED_CHILDREN | SHARED_COUNTS);
totalCount += childPage.totalCount;
addMemory(map.getKeyType().getMemory(key) +
DataUtils.PAGE_MEMORY_CHILD);
......@@ -709,58 +605,32 @@ public class Page {
* @param index the index
*/
public void remove(int index) {
int keyIndex = index >= keyCount ? index - 1 : index;
int keyLength = keys.length;
int keyIndex = index >= keyLength ? index - 1 : index;
Object old = keys[keyIndex];
addMemory(-map.getKeyType().getMemory(old));
if ((sharedFlags & SHARED_KEYS) == 0 &&
keys.length > keyCount - 4) {
if (keyIndex < keyCount - 1) {
System.arraycopy(keys, keyIndex + 1, keys, keyIndex, keyCount -
keyIndex - 1);
}
keys[keyCount - 1] = null;
} else {
Object[] newKeys = new Object[keyCount - 1];
DataUtils.copyExcept(keys, newKeys, keyCount, keyIndex);
keys = newKeys;
sharedFlags &= ~SHARED_KEYS;
}
Object[] newKeys = new Object[keyLength - 1];
DataUtils.copyExcept(keys, newKeys, keyLength, keyIndex);
keys = newKeys;
if (values != null) {
old = values[index];
addMemory(-map.getValueType().getMemory(old));
if ((sharedFlags & SHARED_VALUES) == 0 &&
values.length > keyCount - 4) {
if (index < keyCount - 1) {
System.arraycopy(values, index + 1, values, index,
keyCount - index - 1);
}
values[keyCount - 1] = null;
} else {
Object[] newValues = new Object[keyCount - 1];
DataUtils.copyExcept(values, newValues, keyCount, index);
values = newValues;
sharedFlags &= ~SHARED_VALUES;
}
Object[] newValues = new Object[keyLength - 1];
DataUtils.copyExcept(values, newValues, keyLength, index);
values = newValues;
totalCount--;
}
keyCount--;
if (children != null) {
addMemory(-DataUtils.PAGE_MEMORY_CHILD);
long countOffset = counts[index];
long countOffset = children[index].count;
int childCount = children.length;
PageReference[] newChildren = new PageReference[childCount - 1];
DataUtils.copyExcept(children, newChildren, childCount, index);
children = newChildren;
long[] newCounts = new long[childCount - 1];
DataUtils.copyExcept(counts, newCounts, childCount, index);
counts = newCounts;
sharedFlags &= ~(SHARED_CHILDREN | SHARED_COUNTS);
totalCount -= countOffset;
childCount--;
}
}
......@@ -801,21 +671,19 @@ public class Page {
}
int len = DataUtils.readVarInt(buff);
keys = new Object[len];
keyCount = len;
int type = buff.get();
boolean node = (type & 1) == DataUtils.PAGE_TYPE_NODE;
if (node) {
childCount = len + 1;
children = new PageReference[len + 1];
long[] p = new long[len + 1];
for (int i = 0; i <= len; i++) {
children[i] = new PageReference(null, buff.getLong());
p[i] = buff.getLong();
}
counts = new long[len + 1];
long total = 0;
for (int i = 0; i <= len; i++) {
long s = DataUtils.readVarLong(buff);
total += s;
counts[i] = s;
children[i] = new PageReference(null, p[i], s);
}
totalCount = total;
}
......@@ -855,7 +723,7 @@ public class Page {
*/
private int write(Chunk chunk, WriteBuffer buff) {
int start = buff.position();
int len = keyCount;
int len = keys.length;
int type = children != null ? DataUtils.PAGE_TYPE_NODE
: DataUtils.PAGE_TYPE_LEAF;
buff.putInt(0).
......@@ -867,7 +735,7 @@ public class Page {
if (type == DataUtils.PAGE_TYPE_NODE) {
writeChildren(buff);
for (int i = 0; i <= len; i++) {
buff.putVarLong(counts[i]);
buff.putVarLong(children[i].count);
}
}
int compressStart = buff.position();
......@@ -931,7 +799,7 @@ public class Page {
}
private void writeChildren(WriteBuffer buff) {
int len = keyCount;
int len = keys.length;
for (int i = 0; i <= len; i++) {
buff.putLong(children[i].pos);
}
......@@ -951,12 +819,12 @@ public class Page {
}
int patch = write(chunk, buff);
if (!isLeaf()) {
int len = childCount;
int len = children.length;
for (int i = 0; i < len; i++) {
Page p = children[i].page;
if (p != null) {
p.writeUnsavedRecursive(chunk, buff);
children[i] = new PageReference(p, p.getPos());
children[i] = new PageReference(p, p.getPos(), p.totalCount);
}
}
int old = buff.position();
......@@ -971,7 +839,7 @@ public class Page {
*/
void writeEnd() {
if (!isLeaf()) {
int len = childCount;
int len = children.length;
for (int i = 0; i < len; i++) {
PageReference ref = children[i];
if (ref.page != null) {
......@@ -980,7 +848,7 @@ public class Page {
DataUtils.ERROR_INTERNAL, "Page not written");
}
ref.page.writeEnd();
children[i] = new PageReference(null, ref.pos);
children[i] = new PageReference(null, ref.pos, ref.count);
}
}
}
......@@ -991,7 +859,7 @@ public class Page {
}
public int getRawChildPageCount() {
return childCount;
return children.length;
}
@Override
......@@ -1032,12 +900,12 @@ public class Page {
private void recalculateMemory() {
int mem = DataUtils.PAGE_MEMORY;
DataType keyType = map.getKeyType();
for (int i = 0; i < keyCount; i++) {
for (int i = 0; i < keys.length; i++) {
mem += keyType.getMemory(keys[i]);
}
if (this.isLeaf()) {
DataType valueType = map.getValueType();
for (int i = 0; i < keyCount; i++) {
for (int i = 0; i < keys.length; i++) {
mem += valueType.getMemory(values[i]);
}
} else {
......@@ -1076,9 +944,15 @@ public class Page {
*/
final Page page;
public PageReference(Page page, long pos) {
/**
* The descendant count for this child page.
*/
final long count;
public PageReference(Page page, long pos, long count) {
this.page = page;
this.pos = pos;
this.count = count;
}
}
......
......@@ -121,7 +121,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
}
@Override
protected Object remove(Page p, long writeVersion, Object key) {
protected synchronized Object remove(Page p, long writeVersion, Object key) {
Object result = null;
if (p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
......@@ -139,11 +139,10 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
// this will mark the old page as deleted
// so we need to update the parent in any case
// (otherwise the old page might be deleted again)
Page c = copyOnWrite(cOld, writeVersion);
Page c = cOld.copy(writeVersion);
long oldSize = c.getTotalCount();
result = remove(c, writeVersion, key);
p.setChild(i, c);
p.setCounts(i, c);
if (oldSize == c.getTotalCount()) {
continue;
}
......@@ -190,45 +189,39 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
putOrAdd(key, value, true);
}
private Object putOrAdd(SpatialKey key, V value, boolean alwaysAdd) {
private synchronized Object putOrAdd(SpatialKey key, V value, boolean alwaysAdd) {
beforeWrite();
try {
long v = writeVersion;
Page p = copyOnWrite(root, v);
Object result;
if (alwaysAdd || get(key) == null) {
if (p.getMemory() > store.getPageSplitSize() &&
p.getKeyCount() > 1) {
// only possible if this is the root, else we would have
// split earlier (this requires pageSplitSize is fixed)
long totalCount = p.getTotalCount();
Page split = split(p, v);
Object k1 = getBounds(p);
Object k2 = getBounds(split);
Object[] keys = { k1, k2 };
Page.PageReference[] children = {
new Page.PageReference(p, p.getPos()),
new Page.PageReference(split, split.getPos()),
new Page.PageReference(null, 0)
};
long[] counts = { p.getTotalCount(),
split.getTotalCount(), 0 };
p = Page.create(this, v,
2, keys, null,
3, children, counts,
totalCount, 0, 0);
// now p is a node; continues
}
add(p, v, key, value);
result = null;
} else {
result = set(p, v, key, value);
long v = writeVersion;
Page p = root.copy(v);
Object result;
if (alwaysAdd || get(key) == null) {
if (p.getMemory() > store.getPageSplitSize() &&
p.getKeyCount() > 3) {
// only possible if this is the root, else we would have
// split earlier (this requires pageSplitSize is fixed)
long totalCount = p.getTotalCount();
Page split = split(p, v);
Object k1 = getBounds(p);
Object k2 = getBounds(split);
Object[] keys = { k1, k2 };
Page.PageReference[] children = {
new Page.PageReference(p, p.getPos(), p.getTotalCount()),
new Page.PageReference(split, split.getPos(), split.getTotalCount()),
new Page.PageReference(null, 0, 0)
};
p = Page.create(this, v,
keys, null,
children,
totalCount, 0);
// now p is a node; continues
}
newRoot(p);
return result;
} finally {
afterWrite();
add(p, v, key, value);
result = null;
} else {
result = set(p, v, key, value);
}
newRoot(p);
return result;
}
/**
......@@ -252,10 +245,9 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
if (contains(p, i, key)) {
Page c = p.getChildPage(i);
if (get(c, key) != null) {
c = copyOnWrite(c, writeVersion);
c = c.copy(writeVersion);
Object result = set(c, writeVersion, key, value);
p.setChild(i, c);
p.setCounts(i, c);
return result;
}
}
......@@ -290,14 +282,12 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
}
}
}
Page c = copyOnWrite(p.getChildPage(index), writeVersion);
if (c.getMemory() > store.getPageSplitSize() && c.getKeyCount() > 1) {
Page c = p.getChildPage(index).copy(writeVersion);
if (c.getMemory() > store.getPageSplitSize() && c.getKeyCount() > 4) {
// split on the way down
Page split = split(c, writeVersion);
p = copyOnWrite(p, writeVersion);
p.setKey(index, getBounds(c));
p.setChild(index, c);
p.setCounts(index, c);
p.insertNode(index, getBounds(split), split);
// now we are not sure where to add
add(p, writeVersion, key, value);
......@@ -308,7 +298,6 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
keyType.increaseBounds(bounds, key);
p.setKey(index, bounds);
p.setChild(index, c);
p.setCounts(index, c);
}
private Page split(Page p, long writeVersion) {
......@@ -412,20 +401,17 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
private Page newPage(boolean leaf, long writeVersion) {
Object[] values;
Page.PageReference[] refs;
long[] c;
if (leaf) {
values = new Object[4];
values = Page.EMPTY_OBJECT_ARRAY;
refs = null;
c = null;
} else {
values = null;
refs = new Page.PageReference[] {
new Page.PageReference(null, 0)};
c = new long[1];
new Page.PageReference(null, 0, 0)};
}
return Page.create(this, writeVersion,
0, new Object[4], values,
leaf ? 0 : 1, refs, c, 0, 0, 0);
Page.EMPTY_OBJECT_ARRAY, values,
refs, 0, 0);
}
private static void move(Page source, Page target, int sourceIndex) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论