提交 c662be41 authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: auto-save (every few MBs and every 1-2 seconds)

上级 5c75744e
......@@ -48,6 +48,9 @@ public class MVMap<K, V> extends AbstractMap<K, V>
private boolean closed;
private boolean readOnly;
private volatile boolean writing;
private volatile int writeCount;
protected MVMap(DataType keyType, DataType valueType) {
this.keyType = keyType;
this.valueType = valueType;
......@@ -91,13 +94,17 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/
@SuppressWarnings("unchecked")
public V put(K key, V value) {
checkWrite();
long writeVersion = store.getCurrentVersion();
Page p = copyOnWrite(root, writeVersion);
p = splitRootIfNeeded(p, writeVersion);
Object result = put(p, writeVersion, key, value);
newRoot(p);
return (V) result;
beforeWrite();
try {
long writeVersion = store.getCurrentVersion();
Page p = copyOnWrite(root, writeVersion);
p = splitRootIfNeeded(p, writeVersion);
Object result = put(p, writeVersion, key, value);
newRoot(p);
return (V) result;
} finally {
afterWrite();
}
}
/**
......@@ -488,9 +495,13 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* Remove all entries.
*/
public void clear() {
checkWrite();
root.removeAllRecursive();
newRoot(Page.createEmpty(this, store.getCurrentVersion()));
beforeWrite();
try {
root.removeAllRecursive();
newRoot(Page.createEmpty(this, store.getCurrentVersion()));
} finally {
afterWrite();
}
}
/**
......@@ -498,11 +509,16 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/
public void removeMap() {
checkOpen();
if (this != store.getMetaMap()) {
checkWrite();
if (this == store.getMetaMap()) {
return;
}
beforeWrite();
try {
root.removeAllRecursive();
store.removeMap(id);
close();
} finally {
afterWrite();
}
}
......@@ -527,13 +543,17 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* @return the old value if the key existed, or null otherwise
*/
public V remove(Object key) {
checkWrite();
long writeVersion = store.getCurrentVersion();
Page p = copyOnWrite(root, writeVersion);
@SuppressWarnings("unchecked")
V result = (V) remove(p, writeVersion, key);
newRoot(p);
return result;
beforeWrite();
try {
long writeVersion = store.getCurrentVersion();
Page p = copyOnWrite(root, writeVersion);
@SuppressWarnings("unchecked")
V result = (V) remove(p, writeVersion, key);
newRoot(p);
return result;
} finally {
afterWrite();
}
}
/**
......@@ -616,7 +636,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
result = p.getValue(index);
p.remove(index);
if (p.getKeyCount() == 0) {
removePage(p);
removePage(p.getPos());
}
}
return result;
......@@ -638,7 +658,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
if (p.getKeyCount() == 0) {
p.setChild(index, c);
p.setCounts(index, c);
removePage(p);
removePage(p.getPos());
} else {
p.remove(index);
}
......@@ -673,15 +693,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
}
}
/**
* Check whether this map has any unsaved changes.
*
* @return true if there are unsaved changes.
*/
public boolean hasUnsavedChanges() {
return !oldRoots.isEmpty();
}
/**
* Compare two keys.
*
......@@ -819,30 +830,34 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* @param version the version
*/
void rollbackTo(long version) {
checkWrite();
removeUnusedOldVersions();
if (version <= createVersion) {
// the map is removed later
} else if (root.getVersion() >= version) {
// iterating in descending order -
// this is not terribly efficient if there are many versions
ArrayList<Page> list = oldRoots;
while (list.size() > 0) {
int i = list.size() - 1;
Page p = list.get(i);
root = p;
list.remove(i);
if (p.getVersion() < version) {
break;
beforeWrite();
try {
removeUnusedOldVersions();
if (version <= createVersion) {
// the map is removed later
} else if (root.getVersion() >= version) {
// iterating in descending order -
// this is not terribly efficient if there are many versions
ArrayList<Page> list = oldRoots;
while (list.size() > 0) {
int i = list.size() - 1;
Page p = list.get(i);
root = p;
list.remove(i);
if (p.getVersion() < version) {
break;
}
}
}
} finally {
afterWrite();
}
}
/**
* Forget all old versions.
*/
void removeAllOldVersions() {
private void removeAllOldVersions() {
// create a new instance
// because another thread might iterate over it
oldRoots = new ArrayList<Page>();
......@@ -887,16 +902,44 @@ public class MVMap<K, V> extends AbstractMap<K, V>
}
/**
* Check whether writing is allowed.
* This method is called before writing to the map. The default
* implementation checks whether writing is allowed.
*
* @throws IllegalStateException if the map is read-only
* @throws UnsupportedOperationException if the map is read-only
*/
protected void checkWrite() {
protected void beforeWrite() {
if (readOnly) {
checkOpen();
throw DataUtils.newUnsupportedOperationException(
"This map is read-only");
}
writing = true;
store.beforeWrite();
}
/**
* This method is called after writing to the map.
*/
protected void afterWrite() {
writeCount++;
writing = false;
}
void waitUntilWritten(long version) {
if (root.getVersion() < version) {
// a write will create a new version
return;
}
// wait until writing is done,
// but only for the current write operation
// a bit like a spin lock
int w = writeCount;
while (writing) {
if (writeCount > w) {
return;
}
Thread.yield();
}
}
public int hashCode() {
......@@ -926,8 +969,8 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*
* @param p the page
*/
protected void removePage(Page p) {
store.removePage(p.getPos());
protected void removePage(long pos) {
store.removePage(this, pos);
}
/**
......@@ -1051,15 +1094,18 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* @param newMapName the name name
*/
public void renameMap(String newMapName) {
checkWrite();
store.renameMap(this, newMapName);
beforeWrite();
try {
store.renameMap(this, newMapName);
} finally {
afterWrite();
}
}
public String toString() {
return asString(null);
}
/**
* A builder for maps.
*
......
......@@ -34,35 +34,46 @@ public class MVMapConcurrent<K, V> extends MVMap<K, V> {
@SuppressWarnings("unchecked")
public V put(K key, V value) {
checkWrite();
V result = get(key);
if (value.equals(result)) {
return result;
}
long writeVersion = store.getCurrentVersion();
synchronized (this) {
Page p = copyOnWrite(root, writeVersion);
p = splitRootIfNeeded(p, writeVersion);
result = (V) put(p, writeVersion, key, value);
newRoot(p);
beforeWrite();
try {
// even if the result is the same, we still update the value
// (otherwise compact doesn't work)
get(key);
long writeVersion = store.getCurrentVersion();
synchronized (this) {
Page p = copyOnWrite(root, writeVersion);
p = splitRootIfNeeded(p, writeVersion);
V result = (V) put(p, writeVersion, key, value);
newRoot(p);
return result;
}
} finally {
afterWrite();
}
return result;
}
void waitUntilWritten(long version) {
// no need to wait
}
@SuppressWarnings("unchecked")
public V remove(Object key) {
checkWrite();
V result = get(key);
if (result == null) {
return null;
}
long writeVersion = store.getCurrentVersion();
synchronized (this) {
Page p = copyOnWrite(root, writeVersion);
result = (V) remove(p, writeVersion, key);
newRoot(p);
beforeWrite();
try {
V result = get(key);
if (result == null) {
return null;
}
long writeVersion = store.getCurrentVersion();
synchronized (this) {
Page p = copyOnWrite(root, writeVersion);
result = (V) remove(p, writeVersion, key);
newRoot(p);
}
return result;
} finally {
afterWrite();
}
return result;
}
/**
......
......@@ -267,7 +267,7 @@ public class Page {
* @return a page with the given version
*/
public Page copy(long version) {
map.getStore().removePage(pos);
map.removePage(pos);
Page newPage = create(map, version,
keyCount, keys, values, children, childrenPages,
counts, totalCount,
......@@ -541,14 +541,14 @@ public class Page {
long c = children[i];
int type = DataUtils.getPageType(c);
if (type == DataUtils.PAGE_TYPE_LEAF) {
map.getStore().removePage(c);
map.removePage(c);
} else {
map.readPage(c).removeAllRecursive();
}
}
}
}
map.getStore().removePage(pos);
map.removePage(pos);
}
/**
......
......@@ -150,7 +150,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
result = p.getValue(i);
p.remove(i);
if (p.getKeyCount() == 0) {
removePage(p);
removePage(p.getPos());
}
break;
}
......@@ -170,7 +170,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
// this child was deleted
p.remove(i);
if (p.getKeyCount() == 0) {
removePage(p);
removePage(p.getPos());
}
break;
}
......@@ -211,34 +211,38 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
}
private Object putOrAdd(SpatialKey key, V value, boolean alwaysAdd) {
checkWrite();
long writeVersion = store.getCurrentVersion();
Page p = copyOnWrite(root, writeVersion);
Object result;
if (alwaysAdd || get(key) == null) {
if (p.getMemory() > store.getPageSize() && p.getKeyCount() > 1) {
// only possible if this is the root, else we would have split earlier
// (this requires maxPageSize is fixed)
long totalCount = p.getTotalCount();
Page split = split(p, writeVersion);
Object k1 = getBounds(p);
Object k2 = getBounds(split);
Object[] keys = { k1, k2 };
long[] children = { p.getPos(), split.getPos(), 0 };
Page[] childrenPages = { p, split, null };
long[] counts = { p.getTotalCount(), split.getTotalCount(), 0 };
p = Page.create(this, writeVersion, 2,
keys, null, children, childrenPages, counts,
totalCount, 0, 0);
// now p is a node; continues
beforeWrite();
try {
long writeVersion = store.getCurrentVersion();
Page p = copyOnWrite(root, writeVersion);
Object result;
if (alwaysAdd || get(key) == null) {
if (p.getMemory() > store.getPageSize() && p.getKeyCount() > 1) {
// only possible if this is the root, else we would have split earlier
// (this requires maxPageSize is fixed)
long totalCount = p.getTotalCount();
Page split = split(p, writeVersion);
Object k1 = getBounds(p);
Object k2 = getBounds(split);
Object[] keys = { k1, k2 };
long[] children = { p.getPos(), split.getPos(), 0 };
Page[] childrenPages = { p, split, null };
long[] counts = { p.getTotalCount(), split.getTotalCount(), 0 };
p = Page.create(this, writeVersion, 2,
keys, null, children, childrenPages, counts,
totalCount, 0, 0);
// now p is a node; continues
}
add(p, writeVersion, key, value);
result = null;
} else {
result = set(p, writeVersion, key, value);
}
add(p, writeVersion, key, value);
result = null;
} else {
result = set(p, writeVersion, key, value);
newRoot(p);
return result;
} finally {
afterWrite();
}
newRoot(p);
return result;
}
/**
......
......@@ -206,7 +206,7 @@ public class TestMVStore extends TestBase {
s.store();
s.close();
int[] expectedReadsForCacheSize = {
3412, 2590, 1924, 1440, 1102, 956, 918
3407, 2590, 1924, 1440, 1106, 956, 918
};
for (int cacheSize = 0; cacheSize <= 6; cacheSize += 4) {
s = new MVStore.Builder().
......@@ -723,17 +723,22 @@ public class TestMVStore extends TestBase {
m.put("1", "Hello");
assertEquals(1, s.incrementVersion());
s.rollbackTo(1);
assertEquals(1, s.getCurrentVersion());
assertEquals("Hello", m.get("1"));
long v2 = s.store();
assertEquals(2, v2);
assertEquals(2, s.getCurrentVersion());
assertFalse(s.hasUnsavedChanges());
assertEquals("Hello", m.get("1"));
s.close();
s = openStore(fileName);
assertEquals(2, s.getCurrentVersion());
meta = s.getMetaMap();
m = s.openMap("data");
assertFalse(s.hasUnsavedChanges());
assertEquals("Hello", m.get("1"));
m0 = s.openMap("data0");
MVMap<String, String> m1 = s.openMap("data1");
m.put("1", "Hallo");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论