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