提交 9455decf authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: retain old chunks for 45 seconds by default.

上级 264f02d8
...@@ -71,6 +71,11 @@ public class Chunk { ...@@ -71,6 +71,11 @@ public class Chunk {
*/ */
long version; long version;
/**
* When this chunk was created, in seconds after the store was created.
*/
long time;
Chunk(int id) { Chunk(int id) {
this.id = id; this.id = id;
} }
...@@ -133,6 +138,7 @@ public class Chunk { ...@@ -133,6 +138,7 @@ public class Chunk {
c.maxLength = Long.parseLong(map.get("maxLength")); c.maxLength = Long.parseLong(map.get("maxLength"));
c.maxLengthLive = Long.parseLong(map.get("maxLengthLive")); c.maxLengthLive = Long.parseLong(map.get("maxLengthLive"));
c.metaRootPos = Long.parseLong(map.get("metaRoot")); c.metaRootPos = Long.parseLong(map.get("metaRoot"));
c.time = Long.parseLong(map.get("time"));
c.version = Long.parseLong(map.get("version")); c.version = Long.parseLong(map.get("version"));
return c; return c;
} }
...@@ -157,14 +163,19 @@ public class Chunk { ...@@ -157,14 +163,19 @@ public class Chunk {
public String asString() { public String asString() {
return return
"id:" + id + "," + "id:" + id + "," +
"start:" + start + "," +
"length:" + length + "," + "length:" + length + "," +
"pageCount:" + pageCount + "," +
"maxLength:" + maxLength + "," + "maxLength:" + maxLength + "," +
"maxLengthLive:" + maxLengthLive + "," + "maxLengthLive:" + maxLengthLive + "," +
"metaRoot:" + metaRootPos + "," + "metaRoot:" + metaRootPos + "," +
"pageCount:" + pageCount + "," +
"start:" + start + "," +
"time:" + time + "," +
"version:" + version; "version:" + version;
} }
public String toString() {
return asString();
}
} }
...@@ -999,4 +999,8 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -999,4 +999,8 @@ public class MVMap<K, V> extends AbstractMap<K, V>
return buff.toString(); return buff.toString();
} }
public String toString() {
return asString();
}
} }
...@@ -40,10 +40,6 @@ header: ...@@ -40,10 +40,6 @@ header:
H:3,... H:3,...
TODO: TODO:
- store creation in file header, and seconds since creation
-- in chunk header (plus a counter) - ensure time never goes backwards
- recovery: keep some old chunks; don't overwritten
-- for 5 minutes (configurable)
- allocate memory with Utils.newBytes and so on - allocate memory with Utils.newBytes and so on
- unified exception handling - unified exception handling
- concurrent map; avoid locking during IO (pre-load pages) - concurrent map; avoid locking during IO (pre-load pages)
...@@ -69,7 +65,6 @@ TODO: ...@@ -69,7 +65,6 @@ TODO:
- allow renaming maps - allow renaming maps
- file locking: solve problem that locks are shared for a VM - file locking: solve problem that locks are shared for a VM
- online backup - online backup
- MapFactory is the wrong name (StorePlugin?) or is too flexible: remove?
- store file "header" at the end of each chunk; at the end of the file - store file "header" at the end of each chunk; at the end of the file
- is there a better name for the file header, - is there a better name for the file header,
-- if it's no longer always at the beginning of a file? -- if it's no longer always at the beginning of a file?
...@@ -89,10 +84,12 @@ TODO: ...@@ -89,10 +84,12 @@ 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
- support Object[] and similar serialization by default
- tool to import / manipulate CSV files (maybe concurrently) - tool to import / manipulate CSV files (maybe concurrently)
- map split / merge (fast if no overlap) - map split / merge (fast if no overlap)
- auto-save if there are too many changes (required for StreamStore) - auto-save if there are too many changes (required for StreamStore)
- StreamStore optimization: avoid copying bytes - StreamStore optimization: avoid copying bytes
- unlimited transaction size
*/ */
...@@ -159,7 +156,6 @@ public class MVStore { ...@@ -159,7 +156,6 @@ public class MVStore {
private volatile boolean reuseSpace = true; private volatile boolean reuseSpace = true;
private long retainVersion = -1; private long retainVersion = -1;
private int retainChunk = -1;
private final Compressor compressor = new CompressLZF(); private final Compressor compressor = new CompressLZF();
...@@ -170,6 +166,12 @@ public class MVStore { ...@@ -170,6 +166,12 @@ public class MVStore {
private int fileWriteCount; private int fileWriteCount;
private int unsavedPageCount; private int unsavedPageCount;
/**
* The time the store was created, in seconds since 1970.
*/
private long creationTime;
private int retentionTime = 45;
MVStore(HashMap<String, Object> config) { MVStore(HashMap<String, Object> config) {
this.config = config; this.config = config;
this.fileName = (String) config.get("fileName"); this.fileName = (String) config.get("fileName");
...@@ -398,10 +400,12 @@ public class MVStore { ...@@ -398,10 +400,12 @@ public class MVStore {
} }
fileSize = file.size(); fileSize = file.size();
if (fileSize == 0) { if (fileSize == 0) {
creationTime = 0;
creationTime = getTime();
fileHeader.put("H", "3"); fileHeader.put("H", "3");
fileHeader.put("blockSize", "" + BLOCK_SIZE); fileHeader.put("blockSize", "" + BLOCK_SIZE);
fileHeader.put("format", "1"); fileHeader.put("format", "1");
fileHeader.put("formatRead", "1"); fileHeader.put("creationTime", "" + creationTime);
writeFileHeader(); writeFileHeader();
} else { } else {
readFileHeader(); readFileHeader();
...@@ -449,6 +453,7 @@ public class MVStore { ...@@ -449,6 +453,7 @@ public class MVStore {
String s = new String(headers, i, BLOCK_SIZE, "UTF-8").trim(); String s = new String(headers, i, BLOCK_SIZE, "UTF-8").trim();
fileHeader = DataUtils.parseMap(s); fileHeader = DataUtils.parseMap(s);
rootChunkStart = Long.parseLong(fileHeader.get("rootChunk")); rootChunkStart = Long.parseLong(fileHeader.get("rootChunk"));
creationTime = Long.parseLong(fileHeader.get("creationTime"));
currentVersion = Long.parseLong(fileHeader.get("version")); currentVersion = Long.parseLong(fileHeader.get("version"));
lastMapId = Integer.parseInt(fileHeader.get("lastMapId")); lastMapId = Integer.parseInt(fileHeader.get("lastMapId"));
int check = (int) Long.parseLong(fileHeader.get("fletcher"), 16); int check = (int) Long.parseLong(fileHeader.get("fletcher"), 16);
...@@ -560,19 +565,25 @@ public class MVStore { ...@@ -560,19 +565,25 @@ public class MVStore {
int currentUnsavedPageCount = unsavedPageCount; int currentUnsavedPageCount = unsavedPageCount;
long storeVersion = currentVersion; long storeVersion = currentVersion;
long version = incrementVersion(); long version = incrementVersion();
long time = getTime();
// 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)
Chunk c = chunks.get(lastChunkId); Chunk lastChunk = chunks.get(lastChunkId);
if (c != null) { if (lastChunk != null) {
meta.put("chunk." + c.id, c.asString()); meta.put("chunk." + lastChunk.id, lastChunk.asString());
// never go backward in time
time = Math.max(lastChunk.time, time);
} }
Chunk c;
c = new Chunk(++lastChunkId); c = new Chunk(++lastChunkId);
c.maxLength = Long.MAX_VALUE; c.maxLength = Long.MAX_VALUE;
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;
time = Math.max(0, time - creationTime);
c.time = time;
c.version = version; 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());
...@@ -594,7 +605,7 @@ public class MVStore { ...@@ -594,7 +605,7 @@ public class MVStore {
ArrayList<Integer> removedChunks = New.arrayList(); ArrayList<Integer> removedChunks = New.arrayList();
do { do {
for (Chunk x : chunks.values()) { for (Chunk x : chunks.values()) {
if (x.maxLengthLive == 0 && (retainChunk == -1 || x.id < retainChunk)) { if (x.maxLengthLive == 0 && canOverwriteChunk(x, time)) {
meta.remove("chunk." + x.id); meta.remove("chunk." + x.id);
removedChunks.add(x.id); removedChunks.add(x.id);
} else { } else {
...@@ -679,6 +690,14 @@ public class MVStore { ...@@ -679,6 +690,14 @@ public class MVStore {
return version; return version;
} }
private boolean canOverwriteChunk(Chunk c, long time) {
return c.time + retentionTime <= time;
}
private long getTime() {
return (System.currentTimeMillis() / 1000) - creationTime;
}
private void applyFreedChunks() { private void applyFreedChunks() {
for (HashMap<Integer, Chunk> freed : freedChunks.values()) { for (HashMap<Integer, Chunk> freed : freedChunks.values()) {
for (Chunk f : freed.values()) { for (Chunk f : freed.values()) {
...@@ -822,10 +841,12 @@ public class MVStore { ...@@ -822,10 +841,12 @@ public class MVStore {
// calculate the average max length // calculate the average max length
int averageMaxLength = (int) (maxLengthSum / chunks.size()); int averageMaxLength = (int) (maxLengthSum / chunks.size());
long time = getTime();
// the 'old' list contains the chunks we want to free up // the 'old' list contains the chunks we want to free up
ArrayList<Chunk> old = New.arrayList(); ArrayList<Chunk> old = New.arrayList();
for (Chunk c : chunks.values()) { for (Chunk c : chunks.values()) {
if (retainChunk == -1 || c.id < retainChunk) { if (canOverwriteChunk(c, time)) {
int age = lastChunkId - c.id + 1; int age = lastChunkId - c.id + 1;
c.collectPriority = c.getFillRate() / age; c.collectPriority = c.getFillRate() / age;
old.add(c); old.add(c);
...@@ -1047,27 +1068,32 @@ public class MVStore { ...@@ -1047,27 +1068,32 @@ public class MVStore {
this.reuseSpace = reuseSpace; this.reuseSpace = reuseSpace;
} }
public int getRetainChunk() { public int getRetentionTime() {
return retainChunk; return retentionTime;
} }
/** /**
* Which chunk to retain. If not set, old chunks are re-used as soon as * How long to retain old, persisted chunks, in seconds. Chunks that are
* possible, which may make it impossible to roll back beyond a save * older than this many seconds may be overwritten once they contain no live
* operation, or read a older version before. * data. The default is 45 seconds. It is assumed that a file system and
* hard disk will flush all write buffers after this many seconds at the
* latest. Using a lower value might be dangerous, unless the file system
* and hard disk flush the buffers earlier. To manually flush the buffers,
* use <code>MVStore.getFile().force(true)</code>, however please note that
* according to various tests this does not always work as expected.
* <p> * <p>
* This setting is not persisted. * This setting is not persisted.
* *
* @param retainChunk the earliest chunk to retain (0 to retain all chunks, * @param seconds how many seconds to retain old chunks (0 to overwrite them
* -1 to re-use space as early as possible) * as early as possible)
*/ */
public void setRetainChunk(int retainChunk) { public void setRetentionTime(int seconds) {
this.retainChunk = retainChunk; this.retentionTime = seconds;
} }
/** /**
* Which version to retain. If not set, all versions up to the last stored * Which version to retain in memory. If not set, all versions back to the
* version are retained. * last stored version are retained.
* *
* @param retainVersion the oldest version to retain * @param retainVersion the oldest version to retain
*/ */
......
...@@ -76,7 +76,9 @@ public class MVStoreBuilder { ...@@ -76,7 +76,9 @@ public class MVStoreBuilder {
} }
/** /**
* Enable data compression using the LZF algorithm. * Compress data before writing using the LZF algorithm. This setting only
* affects writes; it is not necessary to enable compression when reading,
* even if compression was enabled when writing.
* *
* @return this * @return this
*/ */
......
...@@ -146,7 +146,11 @@ public class TestMVStore extends TestBase { ...@@ -146,7 +146,11 @@ public class TestMVStore extends TestBase {
private void testFileHeader() { private void testFileHeader() {
String fileName = getBaseDir() + "/testFileHeader.h3"; String fileName = getBaseDir() + "/testFileHeader.h3";
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
long time = System.currentTimeMillis() / 1000;
assertEquals("3", s.getFileHeader().get("H")); assertEquals("3", s.getFileHeader().get("H"));
long creationTime = Long.parseLong(s.getFileHeader()
.get("creationTime"));
assertTrue(Math.abs(time - creationTime) < 5);
s.getFileHeader().put("test", "123"); s.getFileHeader().put("test", "123");
MVMap<Integer, Integer> map = s.openMap("test"); MVMap<Integer, Integer> map = s.openMap("test");
map.put(10, 100); map.put(10, 100);
...@@ -508,7 +512,6 @@ public class TestMVStore extends TestBase { ...@@ -508,7 +512,6 @@ public class TestMVStore extends TestBase {
assertEquals("World", mOld.get("2")); assertEquals("World", mOld.get("2"));
assertTrue(mOld.isReadOnly()); assertTrue(mOld.isReadOnly());
s.getCurrentVersion(); s.getCurrentVersion();
s.setRetainChunk(0);
long old3 = s.store(); long old3 = s.store();
// the old version is still available // the old version is still available
...@@ -549,6 +552,7 @@ public class TestMVStore extends TestBase { ...@@ -549,6 +552,7 @@ public class TestMVStore extends TestBase {
s.close(); s.close();
long len = FileUtils.size(fileName); long len = FileUtils.size(fileName);
s = openStore(fileName); s = openStore(fileName);
s.setRetentionTime(0);
m = s.openMap("data", Integer.class, String.class); m = s.openMap("data", Integer.class, String.class);
m.clear(); m.clear();
s.store(); s.store();
...@@ -592,9 +596,11 @@ public class TestMVStore extends TestBase { ...@@ -592,9 +596,11 @@ public class TestMVStore extends TestBase {
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVMap<String, String> meta; MVMap<String, String> meta;
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
assertEquals(-1, s.getRetainChunk()); assertEquals(45, s.getRetentionTime());
s.setRetainChunk(0); s.setRetentionTime(0);
assertEquals(0, s.getRetainChunk()); assertEquals(0, s.getRetentionTime());
s.setRetentionTime(45);
assertEquals(45, s.getRetentionTime());
assertEquals(0, s.getCurrentVersion()); assertEquals(0, s.getCurrentVersion());
assertFalse(s.hasUnsavedChanges()); assertFalse(s.hasUnsavedChanges());
MVMap<String, String> m = s.openMap("data", String.class, String.class); MVMap<String, String> m = s.openMap("data", String.class, String.class);
...@@ -612,7 +618,6 @@ public class TestMVStore extends TestBase { ...@@ -612,7 +618,6 @@ public class TestMVStore extends TestBase {
s = openStore(fileName); s = openStore(fileName);
assertEquals(2, s.getCurrentVersion()); assertEquals(2, s.getCurrentVersion());
s.setRetainChunk(0);
meta = s.getMetaMap(); meta = s.getMetaMap();
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
m0 = s.openMap("data0", String.class, String.class); m0 = s.openMap("data0", String.class, String.class);
...@@ -632,7 +637,6 @@ public class TestMVStore extends TestBase { ...@@ -632,7 +637,6 @@ public class TestMVStore extends TestBase {
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
s.setRetainChunk(0);
assertEquals(2, s.getCurrentVersion()); assertEquals(2, s.getCurrentVersion());
meta = s.getMetaMap(); meta = s.getMetaMap();
assertTrue(meta.get("map.data") != null); assertTrue(meta.get("map.data") != null);
...@@ -654,14 +658,12 @@ public class TestMVStore extends TestBase { ...@@ -654,14 +658,12 @@ public class TestMVStore extends TestBase {
s = openStore(fileName); s = openStore(fileName);
assertEquals(4, s.getCurrentVersion()); assertEquals(4, s.getCurrentVersion());
s.setRetainChunk(0);
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
m.put("1", "Hi"); m.put("1", "Hi");
s.store(); s.store();
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
s.setRetainChunk(0);
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"));
s.rollbackTo(v4); s.rollbackTo(v4);
...@@ -669,7 +671,6 @@ public class TestMVStore extends TestBase { ...@@ -669,7 +671,6 @@ public class TestMVStore extends TestBase {
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
s.setRetainChunk(0);
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
assertEquals("Hallo", m.get("1")); assertEquals("Hallo", m.get("1"));
s.close(); s.close();
...@@ -722,7 +723,6 @@ public class TestMVStore extends TestBase { ...@@ -722,7 +723,6 @@ public class TestMVStore extends TestBase {
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
MVMap<String, String> m = s.getMetaMap(); MVMap<String, String> m = s.getMetaMap();
s.setRetainChunk(0);
MVMap<String, String> data = s.openMap("data", String.class, String.class); MVMap<String, String> data = s.openMap("data", String.class, String.class);
data.put("1", "Hello"); data.put("1", "Hello");
data.put("2", "World"); data.put("2", "World");
...@@ -740,6 +740,10 @@ public class TestMVStore extends TestBase { ...@@ -740,6 +740,10 @@ public class TestMVStore extends TestBase {
m.get("map.data")); m.get("map.data"));
assertTrue(m.get("root.1").length() > 0); assertTrue(m.get("root.1").length() > 0);
assertTrue(m.containsKey("chunk.1")); assertTrue(m.containsKey("chunk.1"));
assertEquals("id:1,length:281,maxLength:288,maxLengthLive:0," +
"metaRoot:274877910924,pageCount:2," +
"start:8192,time:0,version:1", m.get("chunk.1"));
assertTrue(m.containsKey("chunk.2")); assertTrue(m.containsKey("chunk.2"));
assertEquals(2, s.getCurrentVersion()); assertEquals(2, s.getCurrentVersion());
...@@ -876,6 +880,7 @@ public class TestMVStore extends TestBase { ...@@ -876,6 +880,7 @@ public class TestMVStore extends TestBase {
long initialLength = 0; long initialLength = 0;
for (int j = 0; j < 20; j++) { for (int j = 0; j < 20; j++) {
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
s.setRetentionTime(0);
MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
m.put(j + i, "Hello " + j); m.put(j + i, "Hello " + j);
...@@ -917,6 +922,7 @@ public class TestMVStore extends TestBase { ...@@ -917,6 +922,7 @@ public class TestMVStore extends TestBase {
long initialLength = 0; long initialLength = 0;
for (int j = 0; j < 20; j++) { for (int j = 0; j < 20; j++) {
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
s.setRetentionTime(0);
MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
m.put(i, "Hello"); m.put(i, "Hello");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论