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

A persistent multi-version map: support store() in a background thread

上级 0aa0c2be
...@@ -243,10 +243,14 @@ Caching is done on the page level. ...@@ -243,10 +243,14 @@ Caching is done on the page level.
The page cache is a concurrent LIRS cache, The page cache is a concurrent LIRS cache,
which should be resistant against scan operations. which should be resistant against scan operations.
</p><p> </p><p>
Concurrent modification operations on the maps are currently not supported, Concurrent modification operations on a map are currently not supported
(the same as <code>HashMap</code> and <code>TreeMap</code>),
however it is planned to support an additional map implementation however it is planned to support an additional map implementation
that supports concurrent writes that supports concurrent writes
(at the cost of speed if used in a single thread, same as <code>ConcurrentHashMap</code>). (at the cost of speed if used in a single thread, same as <code>ConcurrentHashMap</code>).
</p><p>
Storing changes can occur concurrently to modifying the data,
as <code>store()</code> operates on a snapshot.
</p> </p>
<h3>Log Structured Storage</h3> <h3>Log Structured Storage</h3>
......
...@@ -682,9 +682,11 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -682,9 +682,11 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* Set the position of the root page. * Set the position of the root page.
* *
* @param rootPos the position, 0 for empty * @param rootPos the position, 0 for empty
* @param version the version of the root
*/ */
void setRootPos(long rootPos) { void setRootPos(long rootPos, long version) {
root = rootPos == 0 ? Page.createEmpty(this, -1) : readPage(rootPos); root = rootPos == 0 ? Page.createEmpty(this, -1) : readPage(rootPos);
root.setVersion(version);
} }
/** /**
...@@ -899,7 +901,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -899,7 +901,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
Page newest = null; Page newest = null;
// need to copy because it can change // need to copy because it can change
Page r = root; Page r = root;
if (r.getVersion() == version) { if (r.getVersion() <= version && r.getVersion() >= 0) {
newest = r; newest = r;
} else { } else {
// find the newest page that has a getVersion() <= version // find the newest page that has a getVersion() <= version
......
...@@ -40,10 +40,9 @@ header: ...@@ -40,10 +40,9 @@ header:
H:3,... H:3,...
TODO: TODO:
- build script
- test concurrent storing in a background thread - test concurrent storing in a background thread
- store store creation in file header, and seconds since creation - store store creation in file header, and seconds since creation
-- in chunk header (plus a counter) -- in chunk header (plus a counter) - ensure time never goes backwards
- recovery: keep some old chunks; don't overwritten - recovery: keep some old chunks; don't overwritten
-- for 5 minutes (configurable) -- for 5 minutes (configurable)
- allocate memory with Utils.newBytes and so on - allocate memory with Utils.newBytes and so on
...@@ -91,7 +90,8 @@ TODO: ...@@ -91,7 +90,8 @@ TODO:
- and maps without keys (counted b-tree) - and maps without keys (counted b-tree)
- use a small object cache (StringCache) - use a small object cache (StringCache)
- dump values - dump values
- tool to import / manipulate CSV files - tool to import / manipulate CSV files (maybe concurrently)
- map split / merge (fast if no overlap)
*/ */
...@@ -216,7 +216,7 @@ public class MVStore { ...@@ -216,7 +216,7 @@ public class MVStore {
String r = oldMeta.get("root." + template.getId()); String r = oldMeta.get("root." + template.getId());
long rootPos = r == null ? 0 : Long.parseLong(r); long rootPos = r == null ? 0 : Long.parseLong(r);
MVMap<?, ?> m = template.openReadOnly(); MVMap<?, ?> m = template.openReadOnly();
m.setRootPos(rootPos); m.setRootPos(rootPos, version);
return (T) m; return (T) m;
} }
...@@ -286,7 +286,7 @@ public class MVStore { ...@@ -286,7 +286,7 @@ public class MVStore {
root = r == null ? 0 : Long.parseLong(r); root = r == null ? 0 : Long.parseLong(r);
} }
m.open(this, c); m.open(this, c);
m.setRootPos(root); m.setRootPos(root, -1);
maps.put(name, m); maps.put(name, m);
return (T) m; return (T) m;
} }
...@@ -317,7 +317,7 @@ public class MVStore { ...@@ -317,7 +317,7 @@ public class MVStore {
} }
c = readChunkHeader(c.start); c = readChunkHeader(c.start);
MVMap<String, String> oldMeta = meta.openReadOnly(); MVMap<String, String> oldMeta = meta.openReadOnly();
oldMeta.setRootPos(c.metaRootPos); oldMeta.setRootPos(c.metaRootPos, version);
return oldMeta; return oldMeta;
} }
...@@ -414,7 +414,7 @@ public class MVStore { ...@@ -414,7 +414,7 @@ public class MVStore {
Chunk header = readChunkHeader(rootChunkStart); Chunk header = readChunkHeader(rootChunkStart);
lastChunkId = header.id; lastChunkId = header.id;
chunks.put(header.id, header); chunks.put(header.id, header);
meta.setRootPos(header.metaRootPos); meta.setRootPos(header.metaRootPos, -1);
Iterator<String> it = meta.keyIterator("chunk."); Iterator<String> it = meta.keyIterator("chunk.");
while (it.hasNext()) { while (it.hasNext()) {
String s = it.next(); String s = it.next();
...@@ -553,6 +553,10 @@ public class MVStore { ...@@ -553,6 +553,10 @@ public class MVStore {
return currentVersion; return currentVersion;
} }
int currentUnsavedPageCount = unsavedPageCount;
long storeVersion = currentVersion;
long version = incrementVersion();
// the last chunk was not completely correct in the last store() // the last chunk was not completely correct in the last store()
// this needs to be updated now (it's better not to update right after // this needs to be updated now (it's better not to update right after
// storing, because that would modify the meta map again) // storing, because that would modify the meta map again)
...@@ -565,7 +569,7 @@ public class MVStore { ...@@ -565,7 +569,7 @@ public class MVStore {
c.maxLengthLive = Long.MAX_VALUE; c.maxLengthLive = Long.MAX_VALUE;
c.start = Long.MAX_VALUE; c.start = Long.MAX_VALUE;
c.length = Integer.MAX_VALUE; c.length = Integer.MAX_VALUE;
c.version = currentVersion + 1; c.version = version;
chunks.put(c.id, c); chunks.put(c.id, c);
meta.put("chunk." + c.id, c.asString()); meta.put("chunk." + c.id, c.asString());
...@@ -574,7 +578,7 @@ public class MVStore { ...@@ -574,7 +578,7 @@ public class MVStore {
if (m == meta || !m.hasUnsavedChanges()) { if (m == meta || !m.hasUnsavedChanges()) {
continue; continue;
} }
Page p = m.getRoot(); Page p = m.openVersion(storeVersion).getRoot();
if (p.getTotalCount() == 0) { if (p.getTotalCount() == 0) {
meta.put("root." + m.getId(), "0"); meta.put("root." + m.getId(), "0");
} else { } else {
...@@ -606,7 +610,7 @@ public class MVStore { ...@@ -606,7 +610,7 @@ public class MVStore {
if (m == meta || !m.hasUnsavedChanges()) { if (m == meta || !m.hasUnsavedChanges()) {
continue; continue;
} }
Page p = m.getRoot(); Page p = m.openVersion(storeVersion).getRoot();
if (p.getTotalCount() > 0) { if (p.getTotalCount() > 0) {
long root = p.writeUnsavedRecursive(c, buff); long root = p.writeUnsavedRecursive(c, buff);
meta.put("root." + m.getId(), "" + root); meta.put("root." + m.getId(), "" + root);
...@@ -654,11 +658,11 @@ public class MVStore { ...@@ -654,11 +658,11 @@ public class MVStore {
rootChunkStart = filePos; rootChunkStart = filePos;
revertTemp(); revertTemp();
long version = incrementVersion();
// write the new version (after the commit) // write the new version (after the commit)
writeFileHeader(); writeFileHeader();
shrinkFileIfPossible(1); shrinkFileIfPossible(1);
unsavedPageCount = 0; // some pages might have been changed in the meantime (in the newest version)
unsavedPageCount = Math.max(0, unsavedPageCount - currentUnsavedPageCount);
return version; return version;
} }
...@@ -756,7 +760,7 @@ public class MVStore { ...@@ -756,7 +760,7 @@ public class MVStore {
return false; return false;
} }
for (MVMap<?, ?> m : mapsChanged.values()) { for (MVMap<?, ?> m : mapsChanged.values()) {
if (m.hasUnsavedChanges()) { if (m == meta || m.hasUnsavedChanges()) {
return true; return true;
} }
} }
...@@ -867,6 +871,9 @@ public class MVStore { ...@@ -867,6 +871,9 @@ public class MVStore {
} }
Chunk.fromHeader(buff, chunk.start); Chunk.fromHeader(buff, chunk.start);
int chunkLength = chunk.length; int chunkLength = chunk.length;
// mark a change, even if it doesn't look like there was a change
// as changes in the metadata alone are not detected
markChanged(meta);
while (buff.position() < chunkLength) { while (buff.position() < chunkLength) {
int start = buff.position(); int start = buff.position();
int pageLength = buff.getInt(); int pageLength = buff.getInt();
...@@ -1161,14 +1168,11 @@ public class MVStore { ...@@ -1161,14 +1168,11 @@ public class MVStore {
if (last != null) { if (last != null) {
if (last.version >= version) { if (last.version >= version) {
revertTemp(); revertTemp();
}
if (last.version > version) {
loadFromFile = true; loadFromFile = true;
while (last != null && last.version > version) { do {
chunks.remove(lastChunkId); last = chunks.remove(lastChunkId);
lastChunkId--; lastChunkId--;
last = chunks.get(lastChunkId); } while (last.version > version && chunks.size() > 0);
}
rootChunkStart = last.start; rootChunkStart = last.start;
writeFileHeader(); writeFileHeader();
readFileHeader(); readFileHeader();
...@@ -1183,7 +1187,7 @@ public class MVStore { ...@@ -1183,7 +1187,7 @@ public class MVStore {
if (loadFromFile) { if (loadFromFile) {
String r = meta.get("root." + m.getId()); String r = meta.get("root." + m.getId());
long root = r == null ? 0 : Long.parseLong(r); long root = r == null ? 0 : Long.parseLong(r);
m.setRootPos(root); m.setRootPos(root, version);
} }
} }
} }
......
...@@ -37,7 +37,7 @@ public class Page { ...@@ -37,7 +37,7 @@ public class Page {
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
private final MVMap<?, ?> map; private final MVMap<?, ?> map;
private final long version; private long version;
private long pos; private long pos;
private long totalCount; private long totalCount;
private int keyCount; private int keyCount;
...@@ -886,4 +886,8 @@ public class Page { ...@@ -886,4 +886,8 @@ public class Page {
return mem; return mem;
} }
void setVersion(long version) {
this.version = version;
}
} }
...@@ -77,7 +77,7 @@ public class ObjectType implements DataType { ...@@ -77,7 +77,7 @@ public class ObjectType implements DataType {
static final int TAG_BYTE_ARRAY_0_15 = 104; static final int TAG_BYTE_ARRAY_0_15 = 104;
/** /**
* Contants for floating point synchronization. * Constants for floating point synchronization.
*/ */
static final int FLOAT_ZERO_BITS = Float.floatToIntBits(0.0f); static final int FLOAT_ZERO_BITS = Float.floatToIntBits(0.0f);
static final int FLOAT_ONE_BITS = Float.floatToIntBits(1.0f); static final int FLOAT_ONE_BITS = Float.floatToIntBits(1.0f);
......
...@@ -111,7 +111,8 @@ public class TestMVStore extends TestBase { ...@@ -111,7 +111,8 @@ public class TestMVStore extends TestBase {
map = s.openMap("test"); map = s.openMap("test");
for (int i = 0; i < 1024; i += 128) { for (int i = 0; i < 1024; i += 128) {
for (int j = 0; j < i; j++) { for (int j = 0; j < i; j++) {
map.get(j); String x = map.get(j);
assertEquals(10240, x.length());
} }
} }
assertEquals(expectedReadsForCacheSize[cacheSize], assertEquals(expectedReadsForCacheSize[cacheSize],
...@@ -410,11 +411,14 @@ public class TestMVStore extends TestBase { ...@@ -410,11 +411,14 @@ public class TestMVStore extends TestBase {
// concurrently with further modifications) // concurrently with further modifications)
// this will print Hello World // this will print Hello World
// System.out.println(oldMap.get(1)); // System.out.println(oldMap.get(1));
assertEquals("Hello", oldMap.get(1));
// System.out.println(oldMap.get(2)); // System.out.println(oldMap.get(2));
assertEquals("World", oldMap.get(2));
oldMap.close(); oldMap.close();
// print the newest version ("Hi") // print the newest version ("Hi")
// System.out.println(map.get(1)); // System.out.println(map.get(1));
assertEquals("Hi", map.get(1));
// close the store - this doesn't write to disk // close the store - this doesn't write to disk
s.close(); s.close();
...@@ -504,12 +508,16 @@ public class TestMVStore extends TestBase { ...@@ -504,12 +508,16 @@ public class TestMVStore extends TestBase {
assertTrue(mOld.isReadOnly()); assertTrue(mOld.isReadOnly());
s.getCurrentVersion(); s.getCurrentVersion();
s.setRetainChunk(0); s.setRetainChunk(0);
long old2 = s.store(); long old3 = s.store();
// the old version is still available // the old version is still available
assertEquals("Hello", mOld.get("1")); assertEquals("Hello", mOld.get("1"));
assertEquals("World", mOld.get("2")); assertEquals("World", mOld.get("2"));
mOld = m.openVersion(old3);
assertEquals("Hallo", mOld.get("1"));
assertEquals("Welt", mOld.get("2"));
m.put("1", "Hi"); m.put("1", "Hi");
assertEquals("Welt", m.remove("2")); assertEquals("Welt", m.remove("2"));
s.store(); s.store();
...@@ -519,7 +527,8 @@ public class TestMVStore extends TestBase { ...@@ -519,7 +527,8 @@ public class TestMVStore extends TestBase {
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
assertEquals("Hi", m.get("1")); assertEquals("Hi", m.get("1"));
assertEquals(null, m.get("2")); assertEquals(null, m.get("2"));
mOld = m.openVersion(old2);
mOld = m.openVersion(old3);
assertEquals("Hallo", mOld.get("1")); assertEquals("Hallo", mOld.get("1"));
assertEquals("Welt", mOld.get("2")); assertEquals("Welt", mOld.get("2"));
s.close(); s.close();
...@@ -635,23 +644,25 @@ public class TestMVStore extends TestBase { ...@@ -635,23 +644,25 @@ public class TestMVStore extends TestBase {
assertFalse(m0.isReadOnly()); assertFalse(m0.isReadOnly());
m.put("1", "Hallo"); m.put("1", "Hallo");
s.incrementVersion(); s.incrementVersion();
assertEquals(3, s.getCurrentVersion()); long v3 = s.getCurrentVersion();
assertEquals(3, v3);
long v4 = s.store(); long v4 = s.store();
assertEquals(4, v4); assertEquals(4, v4);
assertEquals(4, s.getCurrentVersion()); assertEquals(4, s.getCurrentVersion());
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
assertEquals(4, s.getCurrentVersion());
s.setRetainChunk(0); s.setRetainChunk(0);
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
m.put("1", "Hello"); m.put("1", "Hi");
s.store(); s.store();
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
s.setRetainChunk(0); s.setRetainChunk(0);
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
assertEquals("Hello", m.get("1")); assertEquals("Hi", m.get("1"));
s.rollbackTo(v4); s.rollbackTo(v4);
assertEquals("Hallo", m.get("1")); assertEquals("Hallo", m.get("1"));
s.close(); s.close();
...@@ -715,6 +726,10 @@ public class TestMVStore extends TestBase { ...@@ -715,6 +726,10 @@ public class TestMVStore extends TestBase {
data.put("1", "Hello"); data.put("1", "Hello");
data.put("2", "World"); data.put("2", "World");
s.store(); s.store();
assertEquals(1, s.getCurrentVersion());
assertTrue(m.containsKey("chunk.1"));
assertFalse(m.containsKey("chunk.2"));
assertEquals("id:1,name:data,type:btree,createVersion:0,key:,value:", assertEquals("id:1,name:data,type:btree,createVersion:0,key:,value:",
m.get("map.data")); m.get("map.data"));
assertTrue(m.containsKey("chunk.1")); assertTrue(m.containsKey("chunk.1"));
...@@ -725,9 +740,14 @@ public class TestMVStore extends TestBase { ...@@ -725,9 +740,14 @@ public class TestMVStore extends TestBase {
assertTrue(m.get("root.1").length() > 0); assertTrue(m.get("root.1").length() > 0);
assertTrue(m.containsKey("chunk.1")); assertTrue(m.containsKey("chunk.1"));
assertTrue(m.containsKey("chunk.2")); assertTrue(m.containsKey("chunk.2"));
assertEquals(2, s.getCurrentVersion());
s.rollbackTo(1); s.rollbackTo(1);
assertEquals("Hello", data.get("1"));
assertEquals("World", data.get("2"));
assertTrue(m.containsKey("chunk.1")); assertTrue(m.containsKey("chunk.1"));
assertFalse(m.containsKey("chunk.2")); assertFalse(m.containsKey("chunk.2"));
s.close(); s.close();
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论